Skip to content

Commit 1847930

Browse files
author
Dev Agent Amelia
committed
fix: install wizard uses code --add-mcp + profile patching (v0.2.5)
- Rewrite install.ts to use VS Code native --add-mcp CLI flag - Fix Windows: route through cmd.exe /c for .cmd batch files - Patch custom VS Code profiles (--add-mcp only covers default) - Update README with npx mcp-dataverse install as recommended method - Update unit tests for new registerViaCodeCli logic - Bump version to 0.2.5
1 parent 5a2ce1f commit 1847930

6 files changed

Lines changed: 213 additions & 165 deletions

File tree

.vscode/mcp.json

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
11
{
2-
"inputs": [
3-
{
4-
"id": "dataverse_env_url",
5-
"type": "promptString",
6-
"description": "Dataverse environment URL (e.g. https://yourorg.crm.dynamics.com)",
7-
"password": false
8-
}
9-
],
10-
"servers": {}
11-
}
2+
"servers": {}
3+
}

README.md

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,65 @@ MCP server that exposes the Microsoft Dataverse Web API as **50 AI-callable tool
88

99
## Install
1010

11-
### ✅ Recommended — VS Code Command Palette
11+
### ✅ Recommended — Interactive CLI (VS Code 1.99+)
1212

13-
This is the most reliable installation method, confirmed working on both VS Code and VS Code Insiders.
13+
```bash
14+
npx mcp-dataverse install
15+
```
16+
17+
The wizard will:
18+
19+
1. Ask for your Dataverse environment URL
20+
2. Save configuration to `~/.mcp-dataverse/config.json`
21+
3. Register the server in VS Code and/or VS Code Insiders via `code --add-mcp`
22+
4. Authenticate with your Microsoft account (device code flow)
23+
24+
After the wizard completes, the server is immediately available in VS Code — open Copilot chat and the 50 Dataverse tools are ready.
25+
26+
> **Requires VS Code 1.99+** — the CLI registration uses `code --add-mcp`, introduced in March 2025. If neither `code` nor `code-insiders` is on your PATH, the wizard prints a manual snippet to copy-paste. Custom VS Code profiles are also detected and patched automatically.
27+
28+
---
1429

15-
1. Open VS Code
16-
2. Press **Ctrl+Shift+P** (or **Cmd+Shift+P** on macOS)
17-
3. Type **`MCP: Add Server`** and select it
18-
4. Choose **`NPM Package`**
19-
5. Enter the package name: **`mcp-dataverse`**
20-
6. Choose the scope: **User** (available in all workspaces) or **Workspace** (current project only)
30+
### Alternative — VS Code Command Palette
2131

22-
VS Code will create or update `mcp.json` automatically. Then **edit the generated entry** to add your environment URL:
32+
1. Press **Ctrl+Shift+P** (or **Cmd+Shift+P** on macOS)
33+
2. Type **`MCP: Add Server`** → choose **`NPM Package`** → enter **`mcp-dataverse`**
34+
3. Choose the scope: **User** or **Workspace**
35+
4. Edit the generated entry to add your environment URL:
2336

2437
```jsonc
25-
// ~/.config/Code/User/mcp.json (User scope)
26-
// or .vscode/mcp.json (Workspace scope)
2738
{
2839
"servers": {
2940
"mcp-dataverse": {
3041
"type": "stdio",
3142
"command": "npx",
3243
"args": ["-y", "mcp-dataverse"],
3344
"env": {
34-
"DATAVERSE_ENV_URL": "https://yourorg.crm.dynamics.com",
35-
},
36-
},
37-
},
45+
"MCP_CONFIG_PATH": "/path/to/.mcp-dataverse/config.json"
46+
}
47+
}
48+
}
3849
}
3950
```
4051

41-
Replace `https://yourorg.crm.dynamics.com` with your actual Dataverse environment URL.
42-
4352
---
4453

4554
### Manual (mcp.json)
4655

47-
Create or edit `.vscode/mcp.json` in your project (workspace scope) or the user-level `mcp.json`:
56+
Create or edit `.vscode/mcp.json` (workspace scope) or the user-level `mcp.json` (**Ctrl+Shift+P****MCP: Open User Configuration**):
4857

4958
```jsonc
5059
{
5160
"servers": {
52-
"dataverse": {
61+
"mcp-dataverse": {
5362
"type": "stdio",
5463
"command": "npx",
5564
"args": ["-y", "mcp-dataverse"],
5665
"env": {
57-
"DATAVERSE_ENV_URL": "https://yourorg.crm.dynamics.com",
58-
},
59-
},
60-
},
66+
"MCP_CONFIG_PATH": "/path/to/.mcp-dataverse/config.json"
67+
}
68+
}
69+
}
6170
}
6271
```
6372

@@ -313,5 +322,6 @@ GitHub Copilot → stdio → MCP Server → Tool Router → DataverseClient →
313322
| Browser didn't open automatically | Copy the URL from the Output panel manually: `https://microsoft.com/devicelogin` |
314323
| `No MSAL accounts found` | Run `npx mcp-dataverse-auth` to re-authenticate, then restart the server |
315324
| `Authentication timed out` | The 5-minute window expired — restart the MCP server to get a new code |
316-
| `"https://" is required` | Check `DATAVERSE_ENV_URL` in your `mcp.json` — must start with `https://` |
317-
| Server not appearing in Copilot Agent mode | Restart VS Code; check **Output → MCP** panel for errors |
325+
| `"https://" is required` | Check your `~/.mcp-dataverse/config.json` — URL must start with `https://` |
326+
| Server not appearing in Copilot Agent mode | Re-run `npx mcp-dataverse install`; check **Output → MCP** panel for errors |
327+
| Install wizard says "not found on PATH" | Install VS Code Shell Command (**Ctrl+Shift+P***Shell Command: Install...*) or use the manual snippet provided |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mcp-dataverse",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"description": "MCP Server for Microsoft Dataverse Web API",
55
"type": "module",
66
"main": "dist/server.js",

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"url": "https://github.com/codeurali/mcp-dataverse",
1616
"source": "github"
1717
},
18-
"version": "0.2.4",
18+
"version": "0.2.5",
1919
"packages": [
2020
{
2121
"registryType": "npm",
2222
"registryBaseUrl": "https://registry.npmjs.org",
2323
"identifier": "mcp-dataverse",
24-
"version": "0.2.4",
24+
"version": "0.2.5",
2525
"transport": {
2626
"type": "stdio"
2727
},

src/install.ts

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
* Steps:
77
* 1. Prompt for Dataverse environment URL
88
* 2. Save config to ~/.mcp-dataverse/config.json
9-
* 3. Register the server in VS Code user mcp.json
9+
* 3. Register the server in VS Code via `code --add-mcp` CLI
1010
* 4. Authenticate via Microsoft device code flow
1111
*/
1212
import { createInterface } from "readline/promises";
13-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
13+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
14+
import { execFileSync } from "child_process";
1415
import { homedir } from "os";
15-
import { dirname, join } from "path";
16+
import { join } from "path";
1617
import { DeviceCodeAuthProvider } from "./auth/device-code-auth-provider.js";
1718

1819
// ── Constants ─────────────────────────────────────────────────────────────────
@@ -25,57 +26,101 @@ const CONFIG_FILE = join(CACHE_DIR, "config.json");
2526
const out = (msg: string) => process.stdout.write(msg + "\n");
2627
const hr = () => process.stdout.write("─".repeat(58) + "\n");
2728

28-
// ── VS Code mcp.json locations ─────────────────────────────────────────────────
29+
// ── VS Code CLI registration via --add-mcp ─────────────────────────────────────
2930

30-
function getVSCodeUserDirs(): { label: string; mcpJson: string }[] {
31-
const home = homedir();
32-
if (process.platform === "win32") {
33-
const appData = process.env["APPDATA"] ?? join(home, "AppData", "Roaming");
34-
return [
35-
{ label: "VS Code Insiders", mcpJson: join(appData, "Code - Insiders", "User", "mcp.json") },
36-
{ label: "VS Code Stable", mcpJson: join(appData, "Code", "User", "mcp.json") },
37-
];
38-
}
39-
if (process.platform === "darwin") {
40-
const base = join(home, "Library", "Application Support");
41-
return [
42-
{ label: "VS Code Insiders", mcpJson: join(base, "Code - Insiders", "User", "mcp.json") },
43-
{ label: "VS Code Stable", mcpJson: join(base, "Code", "User", "mcp.json") },
44-
];
45-
}
46-
const base = join(home, ".config");
47-
return [
48-
{ label: "VS Code Insiders", mcpJson: join(base, "Code - Insiders", "User", "mcp.json") },
49-
{ label: "VS Code Stable", mcpJson: join(base, "Code", "User", "mcp.json") },
31+
function registerViaCodeCli(configFilePath: string): { label: string; ok: boolean }[] {
32+
const serverDef = JSON.stringify({
33+
name: "mcp-dataverse",
34+
type: "stdio",
35+
command: "npx",
36+
args: ["-y", "mcp-dataverse"],
37+
env: { MCP_CONFIG_PATH: configFilePath },
38+
});
39+
40+
const clis: { label: string; bin: string }[] = [
41+
{ label: "VS Code Insiders", bin: "code-insiders" },
42+
{ label: "VS Code Stable", bin: "code" },
5043
];
44+
45+
const results: { label: string; ok: boolean }[] = [];
46+
for (const { label, bin } of clis) {
47+
try {
48+
// On Windows, VS Code CLI is a .cmd batch file — execFileSync can't run
49+
// .cmd directly, so we go through cmd.exe /c.
50+
const args = process.platform === "win32"
51+
? ["/c", bin, "--add-mcp", serverDef]
52+
: ["--add-mcp", serverDef];
53+
const exe = process.platform === "win32" ? "cmd.exe" : bin;
54+
execFileSync(exe, args, { stdio: "pipe", timeout: 15_000 });
55+
results.push({ label, ok: true });
56+
} catch {
57+
results.push({ label, ok: false });
58+
}
59+
}
60+
return results;
5161
}
5262

53-
// ── mcp.json patching ─────────────────────────────────────────────────────────
63+
// ── Patch custom VS Code profiles ──────────────────────────────────────────────
64+
// `--add-mcp` only writes to the default profile. If the user runs VS Code with
65+
// a custom profile, that profile has its own mcp.json that we need to patch too.
5466

55-
function patchVSCodeMcpJson(configFilePath: string): { label: string; path: string }[] {
56-
const updated: { label: string; path: string }[] = [];
67+
function getVSCodeUserDirs(): { label: string; dir: string }[] {
68+
const home = homedir();
69+
switch (process.platform) {
70+
case "win32": {
71+
const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
72+
return [
73+
{ label: "VS Code Insiders", dir: join(appData, "Code - Insiders", "User") },
74+
{ label: "VS Code Stable", dir: join(appData, "Code", "User") },
75+
];
76+
}
77+
case "darwin":
78+
return [
79+
{ label: "VS Code Insiders", dir: join(home, "Library", "Application Support", "Code - Insiders", "User") },
80+
{ label: "VS Code Stable", dir: join(home, "Library", "Application Support", "Code", "User") },
81+
];
82+
default:
83+
return [
84+
{ label: "VS Code Insiders", dir: join(home, ".config", "Code - Insiders", "User") },
85+
{ label: "VS Code Stable", dir: join(home, ".config", "Code", "User") },
86+
];
87+
}
88+
}
89+
90+
function patchProfileConfigs(configFilePath: string): string[] {
5791
const serverEntry = {
5892
type: "stdio",
5993
command: "npx",
6094
args: ["-y", "mcp-dataverse"],
6195
env: { MCP_CONFIG_PATH: configFilePath },
6296
};
6397

64-
for (const { label, mcpJson } of getVSCodeUserDirs()) {
65-
// Only act when the VS Code User/ directory exists (VS Code variant is installed)
66-
if (!existsSync(dirname(mcpJson))) continue;
67-
68-
let data: Record<string, unknown> = {};
69-
if (existsSync(mcpJson)) {
70-
try { data = JSON.parse(readFileSync(mcpJson, "utf-8")) as Record<string, unknown>; } catch { /* start fresh */ }
98+
const patched: string[] = [];
99+
for (const { dir } of getVSCodeUserDirs()) {
100+
const profilesDir = join(dir, "profiles");
101+
if (!existsSync(profilesDir)) continue;
102+
103+
let entries: string[];
104+
try { entries = readdirSync(profilesDir); } catch { continue; }
105+
106+
for (const profile of entries) {
107+
const mcpPath = join(profilesDir, profile, "mcp.json");
108+
if (!existsSync(mcpPath)) continue;
109+
110+
try {
111+
const raw = readFileSync(mcpPath, "utf-8");
112+
const json = JSON.parse(raw);
113+
if (!json.servers) json.servers = {};
114+
json.servers["mcp-dataverse"] = serverEntry;
115+
writeFileSync(mcpPath, JSON.stringify(json, null, "\t") + "\n", "utf-8");
116+
patched.push(mcpPath);
117+
} catch {
118+
// skip malformed files
119+
}
71120
}
72-
if (!data["servers"] || typeof data["servers"] !== "object") data["servers"] = {};
73-
(data["servers"] as Record<string, unknown>)["mcp-dataverse"] = serverEntry;
74-
writeFileSync(mcpJson, JSON.stringify(data, null, 2) + "\n", "utf-8");
75-
updated.push({ label, path: mcpJson });
76121
}
77122

78-
return updated;
123+
return patched;
79124
}
80125

81126
// ── URL validation ─────────────────────────────────────────────────────────────
@@ -94,7 +139,7 @@ function isValidDataverseUrl(raw: string): string | null {
94139
// ── Manual mcp.json snippet ────────────────────────────────────────────────────
95140

96141
function printManualSnippet(configFilePath: string): void {
97-
out(" Add this to your VS Code user mcp.json (Ctrl+Shift+P → Open User MCP Configuration):");
142+
out(" Add this to your VS Code mcp.json (Ctrl+Shift+P → MCP: Open User Configuration):");
98143
out("");
99144
out(' "mcp-dataverse": {');
100145
out(' "type": "stdio",');
@@ -115,7 +160,7 @@ export async function runInstall(): Promise<void> {
115160
out("This wizard will:");
116161
out(" 1. Ask for your Dataverse environment URL");
117162
out(" 2. Save configuration to ~/.mcp-dataverse/config.json");
118-
out(" 3. Register the server in VS Code (user mcp.json)");
163+
out(" 3. Register the server in VS Code via CLI");
119164
out(" 4. Authenticate with your Microsoft account");
120165
out("");
121166
hr();
@@ -144,16 +189,27 @@ export async function runInstall(): Promise<void> {
144189
out(` ✓ ${CONFIG_FILE}`);
145190
out("");
146191

147-
// ── Step 3: Patch VS Code mcp.json ───────────────────────────────────────────
192+
// ── Step 3: Register in VS Code via --add-mcp CLI ────────────────────────────
148193
out("Registering in VS Code…");
149-
const patched = patchVSCodeMcpJson(CONFIG_FILE);
150-
if (patched.length > 0) {
151-
for (const { label, path } of patched) out(` ✓ ${label}: ${path}`);
194+
const results = registerViaCodeCli(CONFIG_FILE);
195+
const registered = results.filter((r) => r.ok);
196+
197+
if (registered.length > 0) {
198+
for (const { label } of registered) out(` ✓ ${label}: registered via --add-mcp`);
199+
const skipped = results.filter((r) => !r.ok);
200+
for (const { label } of skipped) out(` – ${label}: not found on PATH (skipped)`);
152201
} else {
153-
out(" ℹ No VS Code installation detected — configure manually:");
202+
out(" ⚠ Neither 'code' nor 'code-insiders' found on PATH.");
203+
out(" Register manually:");
154204
out("");
155205
printManualSnippet(CONFIG_FILE);
156206
}
207+
208+
// Also patch any custom VS Code profiles (--add-mcp only covers the default)
209+
const profilePatches = patchProfileConfigs(CONFIG_FILE);
210+
if (profilePatches.length > 0) {
211+
out(` + Patched ${profilePatches.length} custom profile(s)`);
212+
}
157213
out("");
158214
hr();
159215

@@ -170,17 +226,21 @@ export async function runInstall(): Promise<void> {
170226
out("");
171227
out(" ✓ Setup complete!");
172228
out("");
173-
out("Next steps:");
174-
out(" • Reload VS Code: Ctrl+Shift+P → Reload Window");
175-
out(" • MCP Dataverse tools will appear in GitHub Copilot chat (🔧)");
229+
if (registered.length > 0) {
230+
out(" The MCP server is now registered in VS Code.");
231+
out(" Open VS Code → Copilot chat → the 50 Dataverse tools are ready to use.");
232+
} else {
233+
out(" Configuration and authentication are ready.");
234+
out(" Register the server manually (see snippet above), then restart VS Code.");
235+
}
176236
out("");
177237
} catch (err) {
178238
const msg = err instanceof Error ? err.message : String(err);
179239
hr();
180240
out(` ✗ Authentication failed: ${msg}`);
181241
out("");
182-
out(" Configuration was saved. Authenticate later with:");
183-
out(` npx mcp-dataverse-auth ${environmentUrl}`);
242+
out(" Configuration was saved. Authenticate later by restarting the MCP server —");
243+
out(" it will prompt for device code on startup.");
184244
out("");
185245
process.exit(1);
186246
}

0 commit comments

Comments
 (0)