dbatools-mcp-server 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DataPlat contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # dbatools-mcp-server
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for the [dbatools](https://dbatools.io) PowerShell module.
4
+
5
+ Exposes dbatools commands as MCP tools so AI assistants (GitHub Copilot, Claude, etc.) can discover, explain, and execute dbatools commands directly — with all metadata sourced from dbatools' own **comment-based help**.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **`list_dbatools_commands`** — search commands by verb, noun, keyword, or risk level
12
+ - **`get_dbatools_command_help`** — full normalized help (synopsis, parameters, examples) from `Get-Help -Full`
13
+ - **`invoke_dbatools_command`** — execute any dbatools command with safe parameter validation, risk gating, and structured JSON output
14
+ - **`check_dbatools_environment`** — verify PowerShell + dbatools installation, index freshness, and version alignment
15
+ - **Version mismatch detection** — warns when installed dbatools version differs from the indexed version
16
+ - **Safe mode** — non-readonly commands require explicit `confirm: true` to execute
17
+ - **SQL Authentication support** — pass `SqlCredential: { username, password }` for SQL auth instances
18
+
19
+ ---
20
+
21
+ ## Prerequisites
22
+
23
+ - [Node.js](https://nodejs.org/) 20+
24
+ - [PowerShell 7+](https://github.com/PowerShell/PowerShell/releases) (`pwsh`)
25
+ - [dbatools](https://dbatools.io/download) PowerShell module
26
+
27
+ ```powershell
28
+ Install-Module dbatools -Scope CurrentUser
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ```powershell
36
+ # 1. Clone the repo
37
+ git clone https://github.com/Dataplat/dbatools-mcp-server.git
38
+ cd dbatools-mcp-server
39
+
40
+ # 2. Install Node dependencies
41
+ npm install
42
+
43
+ # 3. Generate the help index from your local dbatools installation
44
+ npm run refresh-help
45
+
46
+ # 4. Build
47
+ npm run build
48
+ ```
49
+
50
+ Then open the folder in VS Code — the `.vscode/mcp.json` file automatically registers the MCP server.
51
+
52
+ ---
53
+
54
+ ## Connecting to VS Code
55
+
56
+ The included [`.vscode/mcp.json`](.vscode/mcp.json) registers the server as a local STDIO MCP server.
57
+ Open this folder in VS Code and the server will appear in the GitHub Copilot MCP panel.
58
+
59
+ ```json
60
+ {
61
+ "servers": {
62
+ "dbatools": {
63
+ "type": "stdio",
64
+ "command": "node",
65
+ "args": ["${workspaceFolder}/dist/server.js"],
66
+ "env": {
67
+ "DBATOOLS_SAFE_MODE": "true",
68
+ "MAX_OUTPUT_ROWS": "100",
69
+ "COMMAND_TIMEOUT_SECONDS": "60"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Configuration
79
+
80
+ All settings are controlled via environment variables (set in `.vscode/mcp.json` or your shell):
81
+
82
+ | Variable | Default | Description |
83
+ |---|---|---|
84
+ | `PWSH_EXE` | `pwsh` | Path to PowerShell executable |
85
+ | `DBATOOLS_SAFE_MODE` | `true` | When `true`, non-readonly commands require `confirm: true` |
86
+ | `MAX_OUTPUT_ROWS` | `100` | Maximum rows returned per command execution |
87
+ | `COMMAND_TIMEOUT_SECONDS` | `60` | Seconds before PowerShell process is killed |
88
+
89
+ ---
90
+
91
+ ## Refreshing the Help Index
92
+
93
+ The help index (`generated/dbatools-help.json`) is generated from your locally installed dbatools module.
94
+ Re-run whenever dbatools is updated:
95
+
96
+ ```powershell
97
+ Update-Module dbatools -Scope CurrentUser
98
+ npm run refresh-help
99
+ ```
100
+
101
+ The server detects version mismatches at runtime and warns you when the index is stale.
102
+
103
+ ---
104
+
105
+ ## Risk Levels
106
+
107
+ Commands are automatically classified by verb:
108
+
109
+ | Risk Level | Verbs | Behavior |
110
+ |---|---|---|
111
+ | `readonly` | Get, Test, Find, Compare, … | Always allowed |
112
+ | `change` | Set, New, Add, Copy, Enable, … | Requires `confirm: true` in safe mode |
113
+ | `destructive` | Remove, Drop, Disable, Reset, … | Requires `confirm: true` in safe mode |
114
+
115
+ ---
116
+
117
+ ## SQL Authentication
118
+
119
+ For SQL-auth-only instances (e.g. Docker), pass credentials via the `SqlCredential` parameter:
120
+
121
+ ```json
122
+ {
123
+ "SqlInstance": "localhost,1433",
124
+ "SqlCredential": { "username": "<SqlLogin>", "password": "YourPassword" }
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Project Structure
131
+
132
+ ```
133
+ dbatools-mcp-server/
134
+ ├── src/
135
+ │ ├── server.ts # MCP server entry point, tool definitions
136
+ │ ├── powershell.ts # PowerShell process runner, health checks, version detection
137
+ │ ├── help-indexer.ts # Help manifest loader and command search
138
+ │ ├── tool-registry.ts # Risk classification, safe argument builder
139
+ │ └── types.ts # Shared TypeScript interfaces
140
+ ├── scripts/
141
+ │ └── refresh-help.ps1 # Generates generated/dbatools-help.json
142
+ ├── generated/ # Help index (gitignored, generated locally)
143
+ ├── .vscode/
144
+ │ └── mcp.json # VS Code MCP local server registration
145
+ └── dist/ # Compiled output (gitignored)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Contributing
151
+
152
+ Contributions are welcome! Please open an issue first for significant changes.
153
+
154
+ This project follows the same community spirit as [dbatools](https://github.com/dataplat/dbatools).
155
+
156
+ ---
157
+
158
+ ## License
159
+
160
+ [MIT](LICENSE) — © 2026 DataPlat contributors
@@ -0,0 +1,21 @@
1
+ import type { HelpIndex, HelpManifest, DbatoolsCommandHelp } from "./types.js";
2
+ /**
3
+ * Load and cache the help manifest from generated/dbatools-help.json.
4
+ * Throws an actionable error if the file has not been generated yet.
5
+ */
6
+ export declare function loadHelpManifest(): HelpManifest;
7
+ /** Shorthand to get just the commands map from the manifest. */
8
+ export declare function loadHelpIndex(): HelpIndex;
9
+ export interface SearchOptions {
10
+ verb?: string;
11
+ noun?: string;
12
+ keyword?: string;
13
+ riskLevel?: string;
14
+ limit?: number;
15
+ }
16
+ /**
17
+ * Filter the help index with optional verb / noun / keyword / riskLevel filters.
18
+ * Keyword matches against name, synopsis, and description (case-insensitive).
19
+ */
20
+ export declare function searchCommands(index: HelpIndex, opts: SearchOptions): DbatoolsCommandHelp[];
21
+ //# sourceMappingURL=help-indexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-indexer.d.ts","sourceRoot":"","sources":["../src/help-indexer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAO/E;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,CAa/C;AAED,gEAAgE;AAChE,wBAAgB,aAAa,IAAI,SAAS,CAEzC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,aAAa,GAClB,mBAAmB,EAAE,CA6BvB"}
@@ -0,0 +1,52 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const HELP_INDEX_PATH = join(__dirname, "../generated/dbatools-help.json");
6
+ let cachedManifest = null;
7
+ /**
8
+ * Load and cache the help manifest from generated/dbatools-help.json.
9
+ * Throws an actionable error if the file has not been generated yet.
10
+ */
11
+ export function loadHelpManifest() {
12
+ if (cachedManifest)
13
+ return cachedManifest;
14
+ if (!existsSync(HELP_INDEX_PATH)) {
15
+ throw new Error(`Help index not found at: ${HELP_INDEX_PATH}\n` +
16
+ `Run 'npm run refresh-help' to generate it from your local dbatools installation.`);
17
+ }
18
+ const raw = readFileSync(HELP_INDEX_PATH, "utf-8");
19
+ cachedManifest = JSON.parse(raw);
20
+ return cachedManifest;
21
+ }
22
+ /** Shorthand to get just the commands map from the manifest. */
23
+ export function loadHelpIndex() {
24
+ return loadHelpManifest().commands;
25
+ }
26
+ /**
27
+ * Filter the help index with optional verb / noun / keyword / riskLevel filters.
28
+ * Keyword matches against name, synopsis, and description (case-insensitive).
29
+ */
30
+ export function searchCommands(index, opts) {
31
+ let commands = Object.values(index);
32
+ if (opts.verb) {
33
+ const v = opts.verb.toLowerCase();
34
+ commands = commands.filter((c) => c.verb.toLowerCase() === v);
35
+ }
36
+ if (opts.noun) {
37
+ const n = opts.noun.toLowerCase();
38
+ commands = commands.filter((c) => c.noun.toLowerCase().includes(n));
39
+ }
40
+ if (opts.keyword) {
41
+ const kw = opts.keyword.toLowerCase();
42
+ commands = commands.filter((c) => c.name.toLowerCase().includes(kw) ||
43
+ c.synopsis.toLowerCase().includes(kw) ||
44
+ c.description.toLowerCase().includes(kw) ||
45
+ c.tags.some((t) => t.toLowerCase().includes(kw)));
46
+ }
47
+ if (opts.riskLevel) {
48
+ commands = commands.filter((c) => c.riskLevel === opts.riskLevel);
49
+ }
50
+ return commands.slice(0, opts.limit ?? 50);
51
+ }
52
+ //# sourceMappingURL=help-indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help-indexer.js","sourceRoot":"","sources":["../src/help-indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAGpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,iCAAiC,CAAC,CAAC;AAE3E,IAAI,cAAc,GAAwB,IAAI,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAE1C,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,4BAA4B,eAAe,IAAI;YAC7C,kFAAkF,CACrF,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACnD,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IACjD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa;IAC3B,OAAO,gBAAgB,EAAE,CAAC,QAAQ,CAAC;AACrC,CAAC;AAUD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAgB,EAChB,IAAmB;IAEnB,IAAI,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACtC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CACxB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CACnD,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { PowerShellResult, ServerConfig } from "./types.js";
2
+ /**
3
+ * Resolve runtime configuration from environment variables with safe defaults.
4
+ * All values can be overridden without code changes.
5
+ */
6
+ export declare function getConfig(): ServerConfig;
7
+ /**
8
+ * Spawn a PowerShell process, run the given script, and collect stdout/stderr.
9
+ * Rejects only on process spawn failure; non-zero exit codes are resolved normally
10
+ * so the caller can inspect exitCode and stderr.
11
+ */
12
+ export declare function runPowerShell(script: string, config: ServerConfig): Promise<PowerShellResult>;
13
+ /**
14
+ * Quick health-check: verifies that PowerShell and the dbatools module are
15
+ * available on the current machine.
16
+ */
17
+ export declare function checkDbatools(config: ServerConfig): Promise<{
18
+ ok: boolean;
19
+ version?: string;
20
+ error?: string;
21
+ }>;
22
+ export interface VersionMismatchResult {
23
+ installedVersion: string;
24
+ indexedVersion: string;
25
+ isStale: boolean;
26
+ message: string;
27
+ }
28
+ /**
29
+ * Compare the installed dbatools version against the version stored in the
30
+ * help-index manifest. Returns a structured result with a human-readable
31
+ * warning when the two differ.
32
+ */
33
+ export declare function checkVersionMismatch(config: ServerConfig, indexedVersion: string): Promise<VersionMismatchResult>;
34
+ //# sourceMappingURL=powershell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershell.d.ts","sourceRoot":"","sources":["../src/powershell.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAsBjE;;;GAGG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAOxC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAgE3B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB5D;AAED,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,qBAAqB,CAAC,CAwBhC"}
@@ -0,0 +1,137 @@
1
+ import { spawn } from "child_process";
2
+ /** Maximum total stdout+stderr bytes buffered per PowerShell invocation (10 MB). */
3
+ const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
4
+ /**
5
+ * Parse an integer env var, falling back to `defaultVal` when the value is
6
+ * missing, non-numeric, or outside the given bounds.
7
+ */
8
+ function parseIntEnv(key, defaultVal, min, max) {
9
+ const raw = process.env[key];
10
+ if (!raw)
11
+ return defaultVal;
12
+ const parsed = parseInt(raw, 10);
13
+ if (!Number.isFinite(parsed) || parsed < min || parsed > max)
14
+ return defaultVal;
15
+ return parsed;
16
+ }
17
+ /**
18
+ * Resolve runtime configuration from environment variables with safe defaults.
19
+ * All values can be overridden without code changes.
20
+ */
21
+ export function getConfig() {
22
+ return {
23
+ powershellExe: process.env["PWSH_EXE"] ?? "pwsh",
24
+ safeMode: process.env["DBATOOLS_SAFE_MODE"] !== "false",
25
+ maxOutputRows: parseIntEnv("MAX_OUTPUT_ROWS", 100, 1, 10_000),
26
+ commandTimeout: parseIntEnv("COMMAND_TIMEOUT_SECONDS", 60, 5, 3600),
27
+ };
28
+ }
29
+ /**
30
+ * Spawn a PowerShell process, run the given script, and collect stdout/stderr.
31
+ * Rejects only on process spawn failure; non-zero exit codes are resolved normally
32
+ * so the caller can inspect exitCode and stderr.
33
+ */
34
+ export function runPowerShell(script, config) {
35
+ return new Promise((resolve, reject) => {
36
+ const pwsh = spawn(config.powershellExe, [
37
+ "-NonInteractive",
38
+ "-NoProfile",
39
+ "-NoLogo",
40
+ "-ExecutionPolicy",
41
+ "Bypass",
42
+ "-Command",
43
+ script,
44
+ ], { shell: false });
45
+ let stdout = "";
46
+ let stderr = "";
47
+ let totalBytes = 0;
48
+ let settled = false;
49
+ function fail(err) {
50
+ if (settled)
51
+ return;
52
+ settled = true;
53
+ pwsh.kill("SIGKILL");
54
+ reject(err);
55
+ }
56
+ const timer = setTimeout(() => {
57
+ fail(new Error(`PowerShell process timed out after ${config.commandTimeout}s`));
58
+ }, config.commandTimeout * 1000);
59
+ pwsh.stdout.on("data", (chunk) => {
60
+ totalBytes += chunk.length;
61
+ if (totalBytes > MAX_BUFFER_BYTES) {
62
+ clearTimeout(timer);
63
+ fail(new Error(`PowerShell output exceeded the ${MAX_BUFFER_BYTES / 1024 / 1024} MB safety limit`));
64
+ return;
65
+ }
66
+ stdout += chunk.toString();
67
+ });
68
+ pwsh.stderr.on("data", (chunk) => {
69
+ totalBytes += chunk.length;
70
+ if (totalBytes > MAX_BUFFER_BYTES) {
71
+ clearTimeout(timer);
72
+ fail(new Error(`PowerShell output exceeded the ${MAX_BUFFER_BYTES / 1024 / 1024} MB safety limit`));
73
+ return;
74
+ }
75
+ stderr += chunk.toString();
76
+ });
77
+ pwsh.on("close", (code) => {
78
+ clearTimeout(timer);
79
+ if (settled)
80
+ return;
81
+ settled = true;
82
+ resolve({ stdout, stderr, exitCode: code ?? 1 });
83
+ });
84
+ pwsh.on("error", (err) => {
85
+ clearTimeout(timer);
86
+ fail(new Error(`Failed to launch '${config.powershellExe}': ${err.message}`));
87
+ });
88
+ });
89
+ }
90
+ /**
91
+ * Quick health-check: verifies that PowerShell and the dbatools module are
92
+ * available on the current machine.
93
+ */
94
+ export async function checkDbatools(config) {
95
+ try {
96
+ const result = await runPowerShell(`$m = Get-Module -ListAvailable -Name dbatools | ` +
97
+ `Sort-Object Version -Descending | Select-Object -First 1; ` +
98
+ `if ($m) { Write-Output $m.Version.ToString() } else { exit 1 }`, config);
99
+ if (result.exitCode === 0 && result.stdout.trim()) {
100
+ return { ok: true, version: result.stdout.trim() };
101
+ }
102
+ return {
103
+ ok: false,
104
+ error: "dbatools module not found. Install it with: Install-Module dbatools -Scope CurrentUser",
105
+ };
106
+ }
107
+ catch (e) {
108
+ return { ok: false, error: String(e) };
109
+ }
110
+ }
111
+ /**
112
+ * Compare the installed dbatools version against the version stored in the
113
+ * help-index manifest. Returns a structured result with a human-readable
114
+ * warning when the two differ.
115
+ */
116
+ export async function checkVersionMismatch(config, indexedVersion) {
117
+ const check = await checkDbatools(config);
118
+ if (!check.ok || !check.version) {
119
+ return {
120
+ installedVersion: "unknown",
121
+ indexedVersion,
122
+ isStale: false,
123
+ message: check.error ?? "Could not determine installed dbatools version.",
124
+ };
125
+ }
126
+ const installed = check.version.trim();
127
+ const isStale = installed !== indexedVersion;
128
+ const message = isStale
129
+ ? `⚠️ dbatools version mismatch detected!\n` +
130
+ ` Installed : ${installed}\n` +
131
+ ` Index : ${indexedVersion}\n` +
132
+ ` The help index is stale. Run 'npm run refresh-help' to rebuild it.\n` +
133
+ ` Until then, command metadata may be inaccurate for new/changed commands.`
134
+ : `✅ dbatools version matches index (${installed}).`;
135
+ return { installedVersion: installed, indexedVersion, isStale, message };
136
+ }
137
+ //# sourceMappingURL=powershell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershell.js","sourceRoot":"","sources":["../src/powershell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
package/dist/server.js ADDED
@@ -0,0 +1,330 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { loadHelpIndex, loadHelpManifest, searchCommands } from "./help-indexer.js";
5
+ import { runPowerShell, checkDbatools, getConfig, checkVersionMismatch } from "./powershell.js";
6
+ import { buildPowerShellScript, } from "./tool-registry.js";
7
+ const config = getConfig();
8
+ // Version mismatch state — resolved once at startup, reused by tools
9
+ let versionState = null;
10
+ async function getVersionState() {
11
+ if (versionState)
12
+ return versionState;
13
+ try {
14
+ const manifest = loadHelpManifest();
15
+ versionState = await checkVersionMismatch(config, manifest.dbatoolsVersion);
16
+ }
17
+ catch {
18
+ versionState = {
19
+ installedVersion: "unknown",
20
+ indexedVersion: "unknown",
21
+ isStale: false,
22
+ message: "Help index not yet generated — run 'npm run refresh-help'.",
23
+ };
24
+ }
25
+ return versionState;
26
+ }
27
+ const server = new McpServer({
28
+ name: "dbatools-mcp-server",
29
+ version: "0.1.0",
30
+ });
31
+ // ---------------------------------------------------------------------------
32
+ // Tool: list_dbatools_commands
33
+ // ---------------------------------------------------------------------------
34
+ server.tool("list_dbatools_commands", "Search and list dbatools commands. Filter by verb, noun, keyword, or risk level.", {
35
+ verb: z
36
+ .string()
37
+ .max(50)
38
+ .optional()
39
+ .describe("PowerShell verb (e.g. Get, Set, New, Remove, Test)"),
40
+ noun: z
41
+ .string()
42
+ .max(100)
43
+ .optional()
44
+ .describe("Noun fragment to match (e.g. Database, Login, AgentJob)"),
45
+ keyword: z
46
+ .string()
47
+ .max(200)
48
+ .optional()
49
+ .describe("Keyword to search in name, synopsis, and description"),
50
+ riskLevel: z
51
+ .enum(["readonly", "change", "destructive"])
52
+ .optional()
53
+ .describe("Filter by risk tier"),
54
+ limit: z
55
+ .number()
56
+ .int()
57
+ .min(1)
58
+ .max(200)
59
+ .default(50)
60
+ .describe("Maximum number of results (default 50, max 200)"),
61
+ }, async ({ verb, noun, keyword, riskLevel, limit }) => {
62
+ const vs = await getVersionState();
63
+ const stalePrefix = vs.isStale ? vs.message + "\n\n" : "";
64
+ let index;
65
+ try {
66
+ index = loadHelpIndex();
67
+ }
68
+ catch (e) {
69
+ return { content: [{ type: "text", text: `Help index unavailable: ${String(e)}` }], isError: true };
70
+ }
71
+ const results = searchCommands(index, { verb, noun, keyword, riskLevel, limit });
72
+ if (results.length === 0) {
73
+ return {
74
+ content: [
75
+ { type: "text", text: stalePrefix + "No commands found matching the given filters." },
76
+ ],
77
+ };
78
+ }
79
+ const header = `Found ${results.length} command(s):\n`;
80
+ const rows = results
81
+ .map((c) => `${c.name.padEnd(50)} [${c.riskLevel.padEnd(11)}] ${(c.synopsis ?? "").substring(0, 80)}`)
82
+ .join("\n");
83
+ return { content: [{ type: "text", text: stalePrefix + header + rows }] };
84
+ });
85
+ // ---------------------------------------------------------------------------
86
+ // Tool: get_dbatools_command_help
87
+ // ---------------------------------------------------------------------------
88
+ server.tool("get_dbatools_command_help", "Get the full normalized help for a specific dbatools command, including parameters and examples sourced from comment-based help.", {
89
+ commandName: z
90
+ .string()
91
+ .max(100)
92
+ .describe("Exact command name, e.g. Get-DbaDatabase"),
93
+ }, async ({ commandName }) => {
94
+ const vs = await getVersionState();
95
+ const stalePrefix = vs.isStale ? vs.message + "\n\n" : "";
96
+ let index;
97
+ try {
98
+ index = loadHelpIndex();
99
+ }
100
+ catch (e) {
101
+ return { content: [{ type: "text", text: `Help index unavailable: ${String(e)}` }], isError: true };
102
+ }
103
+ const help = index[commandName];
104
+ if (!help) {
105
+ return {
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: stalePrefix +
110
+ `Command '${commandName}' not found in the help index.\n` +
111
+ `Use list_dbatools_commands to discover available commands.`,
112
+ },
113
+ ],
114
+ isError: true,
115
+ };
116
+ }
117
+ const paramLines = help.parameters.length === 0
118
+ ? " (none documented)"
119
+ : help.parameters
120
+ .map((p) => {
121
+ const req = p.required ? " [REQUIRED]" : "";
122
+ const aliases = p.aliases.length > 0
123
+ ? ` aliases: ${p.aliases.join(", ")}`
124
+ : "";
125
+ const pipeline = p.pipelineInput ? " pipeline input: yes" : "";
126
+ return (` -${p.name} <${p.type}>${req}` +
127
+ `${aliases}${pipeline}\n ${p.description || "(no description)"}`);
128
+ })
129
+ .join("\n\n");
130
+ const exampleLines = help.examples.length === 0
131
+ ? " (none documented)"
132
+ : help.examples
133
+ .slice(0, 5)
134
+ .map((ex, i) => `--- Example ${i + 1}${ex.title ? ": " + ex.title : ""} ---\n` +
135
+ `${ex.code}\n` +
136
+ (ex.remarks ? `Remarks: ${ex.remarks}` : ""))
137
+ .join("\n\n");
138
+ const text = [
139
+ `NAME: ${help.name}`,
140
+ `RISK LEVEL: ${help.riskLevel}`,
141
+ `VERB / NOUN: ${help.verb} / ${help.noun}`,
142
+ ``,
143
+ `SYNOPSIS`,
144
+ `--------`,
145
+ help.synopsis || "(none)",
146
+ ``,
147
+ `DESCRIPTION`,
148
+ `-----------`,
149
+ help.description || "(none)",
150
+ ``,
151
+ `PARAMETERS`,
152
+ `----------`,
153
+ paramLines,
154
+ ``,
155
+ `EXAMPLES`,
156
+ `--------`,
157
+ exampleLines,
158
+ help.relatedLinks.length > 0
159
+ ? `\nRELATED LINKS\n-------------\n${help.relatedLinks.join("\n")}`
160
+ : "",
161
+ ]
162
+ .join("\n")
163
+ .trim();
164
+ return { content: [{ type: "text", text: stalePrefix + text }] };
165
+ });
166
+ // ---------------------------------------------------------------------------
167
+ // Tool: invoke_dbatools_command
168
+ // ---------------------------------------------------------------------------
169
+ server.tool("invoke_dbatools_command", "Execute a dbatools command via PowerShell and return structured JSON output. Non-readonly commands require confirm:true when safe mode is enabled.", {
170
+ commandName: z
171
+ .string()
172
+ .max(100)
173
+ .describe("Exact dbatools command name to execute, e.g. Get-DbaDatabase"),
174
+ parameters: z
175
+ .record(z.unknown())
176
+ .default({})
177
+ .describe("Key-value map of parameters. Strings, numbers, and booleans map directly to PowerShell parameters. " +
178
+ "For SQL authentication pass SqlCredential as an object: { \"username\": \"sa\", \"password\": \"secret\" }. " +
179
+ "Example: { \"SqlInstance\": \"localhost,2022\", \"SqlCredential\": { \"username\": \"sa\", \"password\": \"P@ssw0rd\" } }"),
180
+ confirm: z
181
+ .boolean()
182
+ .default(false)
183
+ .describe("Set to true to allow execution of change/destructive commands (required when safeMode is on)"),
184
+ }, async ({ commandName, parameters, confirm }) => {
185
+ let index;
186
+ try {
187
+ index = loadHelpIndex();
188
+ }
189
+ catch (e) {
190
+ return { content: [{ type: "text", text: `Help index unavailable: ${String(e)}` }], isError: true };
191
+ }
192
+ const help = index[commandName];
193
+ if (!help) {
194
+ return {
195
+ content: [
196
+ {
197
+ type: "text",
198
+ text: `Unknown command: '${commandName}'.\n` +
199
+ `Use list_dbatools_commands to discover available commands.`,
200
+ },
201
+ ],
202
+ isError: true,
203
+ };
204
+ }
205
+ // Safety gate: block non-readonly commands unless confirmed
206
+ if (config.safeMode && help.riskLevel !== "readonly" && !confirm) {
207
+ return {
208
+ content: [
209
+ {
210
+ type: "text",
211
+ text: `Command '${commandName}' has risk level '${help.riskLevel}'.\n` +
212
+ `Set confirm: true to allow execution, or use a readonly command instead.`,
213
+ },
214
+ ],
215
+ isError: true,
216
+ };
217
+ }
218
+ let script;
219
+ try {
220
+ script = buildPowerShellScript(commandName, parameters, config.maxOutputRows);
221
+ }
222
+ catch (e) {
223
+ return {
224
+ content: [{ type: "text", text: `Parameter validation error: ${String(e)}` }],
225
+ isError: true,
226
+ };
227
+ }
228
+ let result;
229
+ try {
230
+ result = await runPowerShell(script, config);
231
+ }
232
+ catch (e) {
233
+ return {
234
+ content: [{ type: "text", text: `PowerShell execution failed: ${String(e)}` }],
235
+ isError: true,
236
+ };
237
+ }
238
+ if (result.exitCode !== 0) {
239
+ return {
240
+ content: [
241
+ {
242
+ type: "text",
243
+ text: `Command failed (exit ${result.exitCode}):\n` +
244
+ (result.stderr || result.stdout || "(no output)"),
245
+ },
246
+ ],
247
+ isError: true,
248
+ };
249
+ }
250
+ // Try to parse structured output
251
+ let parsed = undefined;
252
+ const trimmed = result.stdout.trim();
253
+ if (trimmed) {
254
+ try {
255
+ parsed = JSON.parse(trimmed);
256
+ }
257
+ catch {
258
+ // Not JSON — return raw text
259
+ }
260
+ }
261
+ const recordCount = Array.isArray(parsed)
262
+ ? parsed.length
263
+ : parsed != null
264
+ ? 1
265
+ : 0;
266
+ const summary = `Executed '${commandName}' — ${recordCount} record(s) returned.`;
267
+ const body = parsed
268
+ ? JSON.stringify(parsed, null, 2)
269
+ : trimmed || "(no output)";
270
+ const stderrSection = result.stderr.trim() ? `\n\nSTDERR:\n${result.stderr.trim()}` : "";
271
+ return {
272
+ content: [
273
+ { type: "text", text: `${summary}\n\n${body}${stderrSection}` },
274
+ ],
275
+ };
276
+ });
277
+ // ---------------------------------------------------------------------------
278
+ // Tool: check_dbatools_environment
279
+ // ---------------------------------------------------------------------------
280
+ server.tool("check_dbatools_environment", "Verify that PowerShell and the dbatools module are installed and report the help-index status.", {}, async () => {
281
+ const check = await checkDbatools(config);
282
+ if (!check.ok) {
283
+ return {
284
+ content: [
285
+ {
286
+ type: "text",
287
+ text: `dbatools not available: ${check.error}`,
288
+ },
289
+ ],
290
+ isError: true,
291
+ };
292
+ }
293
+ let indexInfo = "Help index: not generated yet (run 'npm run refresh-help')";
294
+ let vsMismatch = "";
295
+ try {
296
+ const manifest = loadHelpManifest();
297
+ indexInfo =
298
+ `Help index: ${manifest.commandCount} commands indexed\n` +
299
+ ` dbatools version in index: ${manifest.dbatoolsVersion}\n` +
300
+ ` Generated at: ${manifest.generatedAt}`;
301
+ const vs = await checkVersionMismatch(config, manifest.dbatoolsVersion);
302
+ // Invalidate cached state so next tool call re-evaluates
303
+ versionState = vs;
304
+ vsMismatch = vs.message;
305
+ }
306
+ catch {
307
+ // Index missing — that's fine, non-fatal
308
+ }
309
+ return {
310
+ content: [
311
+ {
312
+ type: "text",
313
+ text: [
314
+ `dbatools ${check.version} is installed and ready.`,
315
+ indexInfo,
316
+ vsMismatch,
317
+ `Safe mode: ${config.safeMode ? "ON (non-readonly commands require confirm:true)" : "OFF"}`,
318
+ `Max output rows: ${config.maxOutputRows}`,
319
+ `Timeout: ${config.commandTimeout}s`,
320
+ ].filter(Boolean).join("\n"),
321
+ },
322
+ ],
323
+ };
324
+ });
325
+ // ---------------------------------------------------------------------------
326
+ // Start
327
+ // ---------------------------------------------------------------------------
328
+ const transport = new StdioServerTransport();
329
+ await server.connect(transport);
330
+ //# 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,34 @@
1
+ import type { DbatoolsCommandHelp } from "./types.js";
2
+ /**
3
+ * Classify a dbatools command into a risk tier based on its verb.
4
+ * readonly — safe to run without confirmation
5
+ * change — modifies state but is reversible
6
+ * destructive — data loss risk; blocked by safe mode until confirm:true
7
+ */
8
+ export declare function classifyCommand(name: string): "readonly" | "change" | "destructive";
9
+ /**
10
+ * Credential descriptor accepted in the `parameters` map under the key `SqlCredential`.
11
+ * Pass as: { "SqlCredential": { "username": "sa", "password": "secret" } }
12
+ */
13
+ export interface SqlCredentialDescriptor {
14
+ username: string;
15
+ password: string;
16
+ }
17
+ /**
18
+ * Build a self-contained PowerShell script that imports dbatools, invokes the
19
+ * requested command with the supplied parameters, and emits JSON via
20
+ * ConvertTo-Json so the MCP server gets structured output.
21
+ *
22
+ * String values are single-quoted and internal single-quotes are escaped.
23
+ * Switch parameters are passed as bare flags when value is true.
24
+ *
25
+ * Special parameter: SqlCredential — accepts { username, password } and is
26
+ * converted into a PSCredential object so SQL authentication works.
27
+ */
28
+ export declare function buildPowerShellScript(commandName: string, args: Record<string, unknown>, maxRows: number): string;
29
+ /**
30
+ * Format a DbatoolsCommandHelp record into a concise MCP tool description
31
+ * (≤ 1024 chars, which is the MCP spec recommendation).
32
+ */
33
+ export declare function buildToolDescription(cmd: DbatoolsCommandHelp): string;
34
+ //# sourceMappingURL=tool-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAWtD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GACX,UAAU,GAAG,QAAQ,GAAG,aAAa,CAKvC;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,MAAM,GACd,MAAM,CAuDR;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAQrE"}
@@ -0,0 +1,102 @@
1
+ const READONLY_VERBS = new Set([
2
+ "Get", "Test", "Find", "Measure", "Select", "Show",
3
+ "Watch", "Compare", "Search", "Resolve",
4
+ ]);
5
+ const DESTRUCTIVE_VERBS = new Set([
6
+ "Remove", "Drop", "Delete", "Uninstall", "Revoke", "Disable", "Reset",
7
+ ]);
8
+ /**
9
+ * Classify a dbatools command into a risk tier based on its verb.
10
+ * readonly — safe to run without confirmation
11
+ * change — modifies state but is reversible
12
+ * destructive — data loss risk; blocked by safe mode until confirm:true
13
+ */
14
+ export function classifyCommand(name) {
15
+ const verb = name.split("-")[0] ?? "";
16
+ if (READONLY_VERBS.has(verb))
17
+ return "readonly";
18
+ if (DESTRUCTIVE_VERBS.has(verb))
19
+ return "destructive";
20
+ return "change";
21
+ }
22
+ function isSqlCredentialDescriptor(v) {
23
+ return (typeof v === "object" &&
24
+ v !== null &&
25
+ typeof v["username"] === "string" &&
26
+ typeof v["password"] === "string");
27
+ }
28
+ /**
29
+ * Build a self-contained PowerShell script that imports dbatools, invokes the
30
+ * requested command with the supplied parameters, and emits JSON via
31
+ * ConvertTo-Json so the MCP server gets structured output.
32
+ *
33
+ * String values are single-quoted and internal single-quotes are escaped.
34
+ * Switch parameters are passed as bare flags when value is true.
35
+ *
36
+ * Special parameter: SqlCredential — accepts { username, password } and is
37
+ * converted into a PSCredential object so SQL authentication works.
38
+ */
39
+ export function buildPowerShellScript(commandName, args, maxRows) {
40
+ // Validate command name against an allowlist pattern (letters, digits, hyphens only)
41
+ if (!/^[A-Za-z]+-[A-Za-z][A-Za-z0-9]*$/.test(commandName)) {
42
+ throw new Error(`Invalid command name: ${commandName}`);
43
+ }
44
+ const preambleLines = [];
45
+ const splatEntries = [];
46
+ for (const [key, value] of Object.entries(args)) {
47
+ // Validate parameter key (letters, digits only)
48
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(key)) {
49
+ throw new Error(`Invalid parameter name: ${key}`);
50
+ }
51
+ if (value === null || value === undefined)
52
+ continue;
53
+ // Special case: SqlCredential object → build a PSCredential in the script
54
+ if (key === "SqlCredential" && isSqlCredentialDescriptor(value)) {
55
+ const escapedUser = value.username.replace(/'/g, "''");
56
+ const escapedPass = value.password.replace(/'/g, "''");
57
+ preambleLines.push(`$__securePass = ConvertTo-SecureString '${escapedPass}' -AsPlainText -Force`, `$__cred = New-Object System.Management.Automation.PSCredential('${escapedUser}', $__securePass)`);
58
+ splatEntries.push(` SqlCredential = $__cred`);
59
+ continue;
60
+ }
61
+ if (typeof value === "boolean") {
62
+ if (value)
63
+ splatEntries.push(` ${key} = $true`);
64
+ }
65
+ else if (typeof value === "number") {
66
+ if (!Number.isFinite(value))
67
+ throw new Error(`Non-finite number for -${key}`);
68
+ splatEntries.push(` ${key} = ${value}`);
69
+ }
70
+ else {
71
+ // Sanitize string: escape embedded single-quotes
72
+ const escaped = String(value).replace(/'/g, "''");
73
+ splatEntries.push(` ${key} = '${escaped}'`);
74
+ }
75
+ }
76
+ const splatBlock = splatEntries.length > 0
77
+ ? [`$params = @{`, ...splatEntries, `}`].join("\n")
78
+ : `$params = @{}`;
79
+ return [
80
+ `Set-StrictMode -Off`,
81
+ `$ErrorActionPreference = 'Stop'`,
82
+ `Import-Module dbatools -ErrorAction Stop`,
83
+ ...preambleLines,
84
+ splatBlock,
85
+ `$result = ${commandName} @params | Select-Object -First ${maxRows}`,
86
+ `if ($null -eq $result) { Write-Output '[]'; exit 0 }`,
87
+ `$result | ConvertTo-Json -Depth 5 -Compress`,
88
+ ].join("\n");
89
+ }
90
+ /**
91
+ * Format a DbatoolsCommandHelp record into a concise MCP tool description
92
+ * (≤ 1024 chars, which is the MCP spec recommendation).
93
+ */
94
+ export function buildToolDescription(cmd) {
95
+ const base = cmd.synopsis || cmd.description || cmd.name;
96
+ const risk = cmd.riskLevel === "readonly"
97
+ ? ""
98
+ : ` [${cmd.riskLevel.toUpperCase()} — requires confirm:true]`;
99
+ const full = `${base}${risk}`;
100
+ return full.length > 1024 ? full.substring(0, 1021) + "..." : full;
101
+ }
102
+ //# sourceMappingURL=tool-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /** Represents one parameter from Get-Help -Full output */
2
+ export interface DbatoolsParameter {
3
+ name: string;
4
+ type: string;
5
+ required: boolean;
6
+ aliases: string[];
7
+ pipelineInput: boolean;
8
+ description: string;
9
+ defaultValue?: string;
10
+ }
11
+ /** One example block from Get-Help -Full output */
12
+ export interface DbatoolsExample {
13
+ title: string;
14
+ code: string;
15
+ remarks: string;
16
+ }
17
+ /** Full normalized help payload for one dbatools command */
18
+ export interface DbatoolsCommandHelp {
19
+ name: string;
20
+ verb: string;
21
+ noun: string;
22
+ synopsis: string;
23
+ description: string;
24
+ parameters: DbatoolsParameter[];
25
+ examples: DbatoolsExample[];
26
+ relatedLinks: string[];
27
+ tags: string[];
28
+ riskLevel: "readonly" | "change" | "destructive";
29
+ }
30
+ /** The full dbatools-help.json manifest written by refresh-help.ps1 */
31
+ export interface HelpManifest {
32
+ generatedAt: string;
33
+ dbatoolsVersion: string;
34
+ commandCount: number;
35
+ commands: HelpIndex;
36
+ }
37
+ /** Keyed by command name, e.g. "Get-DbaDatabase" */
38
+ export type HelpIndex = Record<string, DbatoolsCommandHelp>;
39
+ /** Result returned from running a PowerShell script */
40
+ export interface PowerShellResult {
41
+ stdout: string;
42
+ stderr: string;
43
+ exitCode: number;
44
+ }
45
+ /** Runtime configuration resolved from environment variables */
46
+ export interface ServerConfig {
47
+ powershellExe: string;
48
+ /** When true, non-readonly commands require confirm:true */
49
+ safeMode: boolean;
50
+ /** Maximum rows piped through Select-Object before ConvertTo-Json */
51
+ maxOutputRows: number;
52
+ /** Seconds before the PowerShell child process is killed */
53
+ commandTimeout: number;
54
+ }
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC;CAClD;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,oDAAoD;AACpD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE5D,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;CACxB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ # This directory is created by scripts/refresh-help.ps1
2
+ # dbatools-help.json is generated locally and is intentionally gitignored.
3
+ # Run: npm run refresh-help
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "dbatools-mcp-server",
3
+ "mcpName": "io.github.dataplat/dbatools-mcp-server",
4
+ "version": "0.1.0",
5
+ "description": "MCP server for the dbatools PowerShell module — exposes dbatools commands as MCP tools driven by comment-based help",
6
+ "keywords": [
7
+ "dbatools",
8
+ "mcp",
9
+ "sql server",
10
+ "powershell",
11
+ "database"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/dataplat/dbatools-mcp-server.git"
16
+ },
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "files": [
20
+ "dist",
21
+ "generated/.gitkeep",
22
+ "scripts/refresh-help.ps1"
23
+ ],
24
+ "bin": {
25
+ "dbatools-mcp-server": "./dist/server.js"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "tsx src/server.ts",
30
+ "start": "node dist/server.js",
31
+ "refresh-help": "pwsh -NonInteractive -File scripts/refresh-help.ps1",
32
+ "test": "node --experimental-vm-modules node_modules/.bin/jest --passWithNoTests"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.10.0",
36
+ "zod": "^3.24.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/jest": "^29.5.0",
40
+ "@types/node": "^22.0.0",
41
+ "jest": "^29.7.0",
42
+ "ts-jest": "^29.2.0",
43
+ "tsx": "^4.19.0",
44
+ "typescript": "^5.7.0"
45
+ },
46
+ "jest": {
47
+ "preset": "ts-jest/presets/default-esm",
48
+ "extensionsToTreatAsEsm": [
49
+ ".ts"
50
+ ],
51
+ "moduleNameMapper": {
52
+ "^(\\.{1,2}/.*)\\.js$": "$1"
53
+ },
54
+ "transform": {
55
+ "^.+\\.tsx?$": [
56
+ "ts-jest",
57
+ {
58
+ "useESM": true
59
+ }
60
+ ]
61
+ }
62
+ },
63
+ "engines": {
64
+ "node": ">=20.0.0"
65
+ }
66
+ }
@@ -0,0 +1,192 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Generates generated/dbatools-help.json by extracting comment-based help from
4
+ every command in the locally installed dbatools module.
5
+
6
+ .DESCRIPTION
7
+ Run this script whenever dbatools is installed or updated.
8
+ Output is consumed by the MCP server at startup and cached for the session.
9
+
10
+ Usage: npm run refresh-help
11
+ pwsh -File scripts/refresh-help.ps1
12
+
13
+ .PARAMETER OutputPath
14
+ Override the output file path (default: <repo-root>/generated/dbatools-help.json)
15
+
16
+ .PARAMETER MaxCommands
17
+ Limit to N commands for quick development iterations (0 = all commands)
18
+ #>
19
+ [CmdletBinding()]
20
+ param(
21
+ [string]$OutputPath = "",
22
+ [int]$MaxCommands = 0
23
+ )
24
+
25
+ $ErrorActionPreference = 'Stop'
26
+ $InformationPreference = 'Continue'
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Resolve paths
30
+ # ---------------------------------------------------------------------------
31
+ $ScriptDir = Split-Path -Parent $PSCommandPath
32
+ $RepoRoot = Split-Path -Parent $ScriptDir
33
+ $OutputDir = Join-Path $RepoRoot 'generated'
34
+ $DefaultOut = Join-Path $OutputDir 'dbatools-help.json'
35
+
36
+ if ($OutputPath -eq "") { $OutputPath = $DefaultOut }
37
+
38
+ if (-not (Test-Path $OutputDir)) {
39
+ New-Item -ItemType Directory -Path $OutputDir | Out-Null
40
+ Write-Information "Created directory: $OutputDir"
41
+ }
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Verify dbatools
45
+ # ---------------------------------------------------------------------------
46
+ $module = Get-Module -ListAvailable -Name dbatools |
47
+ Sort-Object Version -Descending |
48
+ Select-Object -First 1
49
+
50
+ if (-not $module) {
51
+ Write-Error "dbatools is not installed.`nRun: Install-Module dbatools -Scope CurrentUser"
52
+ exit 1
53
+ }
54
+
55
+ Write-Information "Found dbatools $($module.Version) at $($module.ModuleBase)"
56
+ Write-Information "Importing module..."
57
+ Import-Module dbatools -ErrorAction Stop
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Enumerate commands
61
+ # ---------------------------------------------------------------------------
62
+ $commands = Get-Command -Module dbatools | Sort-Object Name
63
+
64
+ if ($MaxCommands -gt 0) {
65
+ $commands = $commands | Select-Object -First $MaxCommands
66
+ Write-Warning "MaxCommands=$MaxCommands — indexing a subset only."
67
+ }
68
+
69
+ $total = $commands.Count
70
+ Write-Information "Indexing $total commands..."
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Risk classification helpers
74
+ # ---------------------------------------------------------------------------
75
+ $ReadonlyVerbs = @('Get','Test','Find','Measure','Select','Show','Watch','Compare','Search','Resolve')
76
+ $DestructiveVerbs = @('Remove','Drop','Delete','Uninstall','Revoke','Disable','Reset')
77
+
78
+ function Get-RiskLevel([string]$verb) {
79
+ if ($ReadonlyVerbs -contains $verb) { return 'readonly' }
80
+ if ($DestructiveVerbs -contains $verb) { return 'destructive' }
81
+ return 'change'
82
+ }
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Help extraction loop
86
+ # ---------------------------------------------------------------------------
87
+ $index = [System.Collections.Specialized.OrderedDictionary]::new()
88
+ $failures = 0
89
+ $counter = 0
90
+
91
+ foreach ($cmd in $commands) {
92
+ $counter++
93
+ if ($counter % 100 -eq 0) {
94
+ Write-Information " $counter / $total ($([Math]::Round($counter/$total*100))%)"
95
+ }
96
+
97
+ try {
98
+ $help = Get-Help $cmd.Name -Full -ErrorAction SilentlyContinue
99
+
100
+ # --- Parameters ---------------------------------------------------
101
+ $params = [System.Collections.Generic.List[hashtable]]::new()
102
+ if ($help.parameters -and $help.parameters.parameter) {
103
+ foreach ($p in $help.parameters.parameter) {
104
+ $desc = if ($p.description) {
105
+ ($p.description | ForEach-Object { $_.Text }) -join ' '
106
+ } else { '' }
107
+
108
+ $aliasArr = if ($p.aliases -and $p.aliases -ne 'None') {
109
+ @($p.aliases -split ',\s*' | Where-Object { $_ -ne '' })
110
+ } else { @() }
111
+
112
+ $params.Add([ordered]@{
113
+ name = [string]$p.name
114
+ type = if ($p.type -and $p.type.name) { [string]$p.type.name } else { 'Object' }
115
+ required = ($p.required -eq 'true')
116
+ aliases = $aliasArr
117
+ pipelineInput = ($p.pipelineInput -and $p.pipelineInput -ne 'false')
118
+ description = $desc.Trim()
119
+ defaultValue = if ($p.defaultValue) { [string]$p.defaultValue } else { $null }
120
+ })
121
+ }
122
+ }
123
+
124
+ # --- Examples -----------------------------------------------------
125
+ $examples = [System.Collections.Generic.List[hashtable]]::new()
126
+ if ($help.examples -and $help.examples.example) {
127
+ foreach ($ex in $help.examples.example) {
128
+ $remarks = if ($ex.remarks) {
129
+ ($ex.remarks | ForEach-Object { $_.Text }) -join ' '
130
+ } else { '' }
131
+
132
+ $examples.Add([ordered]@{
133
+ title = ([string]($ex.title ?? '')).TrimStart('-').Trim()
134
+ code = ([string]($ex.code ?? '')).Trim()
135
+ remarks = $remarks.Trim()
136
+ })
137
+ }
138
+ }
139
+
140
+ # --- Related links ------------------------------------------------
141
+ $links = @()
142
+ if ($help.relatedLinks -and $help.relatedLinks.navigationLink) {
143
+ $links = @(
144
+ $help.relatedLinks.navigationLink |
145
+ ForEach-Object { if ($_.uri) { $_.uri } elseif ($_.linkText) { $_.linkText } } |
146
+ Where-Object { $_ -and $_ -ne '' }
147
+ )
148
+ }
149
+
150
+ # --- Synopsis / Description ---------------------------------------
151
+ $synopsis = if ($help.Synopsis) { $help.Synopsis.Trim() } else { '' }
152
+
153
+ $description = if ($help.description) {
154
+ ($help.description | ForEach-Object { $_.Text }) -join ' '
155
+ } else { '' }
156
+
157
+ $index[$cmd.Name] = [ordered]@{
158
+ name = $cmd.Name
159
+ verb = $cmd.Verb
160
+ noun = $cmd.Noun
161
+ synopsis = $synopsis
162
+ description = $description.Trim()
163
+ parameters = $params.ToArray()
164
+ examples = $examples.ToArray()
165
+ relatedLinks = $links
166
+ tags = @()
167
+ riskLevel = Get-RiskLevel $cmd.Verb
168
+ }
169
+ }
170
+ catch {
171
+ $failures++
172
+ Write-Warning "[$counter/$total] Failed to get help for $($cmd.Name): $_"
173
+ }
174
+ }
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # Write manifest
178
+ # ---------------------------------------------------------------------------
179
+ $manifest = [ordered]@{
180
+ generatedAt = (Get-Date -Format 'o')
181
+ dbatoolsVersion = $module.Version.ToString()
182
+ commandCount = $index.Count
183
+ commands = $index
184
+ }
185
+
186
+ $manifest | ConvertTo-Json -Depth 12 | Set-Content -Path $OutputPath -Encoding UTF8
187
+
188
+ Write-Information ""
189
+ Write-Information "Done! Indexed $($index.Count) commands -> $OutputPath"
190
+ if ($failures -gt 0) {
191
+ Write-Warning "$failures command(s) failed to index (see warnings above)."
192
+ }