brightspace-mcp-server 5.0.9 → 5.0.11

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/build/index.js CHANGED
@@ -30,6 +30,20 @@ const PKG_VERSION = (() => {
30
30
  })();
31
31
  // ── Subcommand routing (before any MCP initialization) ──────────────
32
32
  const subcommand = process.argv[2];
33
+ const COMMANDS = ['setup', 'auth', 'doctor', 'http'];
34
+ // A mistyped command (`htp`) must not quietly start a stdio server that sits
35
+ // waiting on stdin. Flags alone still mean the default stdio server.
36
+ if (subcommand !== undefined && !subcommand.startsWith('-') && !COMMANDS.includes(subcommand)) {
37
+ process.stderr.write(`Unknown command: ${subcommand}\n\n` +
38
+ 'Usage: brightspace-mcp-server [command]\n\n' +
39
+ 'Commands:\n' +
40
+ ' (none) run the MCP server on stdio (what MCP clients launch)\n' +
41
+ ' http run one shared MCP server on localhost\n' +
42
+ ' setup configure your school and sign-in\n' +
43
+ ' auth sign in to Brightspace now\n' +
44
+ ' doctor check the install and configuration\n');
45
+ process.exit(2);
46
+ }
33
47
  if (subcommand === 'setup') {
34
48
  await import('./setup.js');
35
49
  }
@@ -51,15 +51,21 @@ function isSharedServerUrl(url) {
51
51
  export function classifyRegistration(entry, platform = process.platform) {
52
52
  if (entry === undefined)
53
53
  return { state: "missing" };
54
- if (entry === null || typeof entry !== "object") {
55
- return { state: "different", current: "(an entry setup could not read)" };
56
- }
54
+ const unreadable = { state: "different", current: "(an entry setup could not read)" };
55
+ if (entry === null || typeof entry !== "object")
56
+ return unreadable;
57
57
  const { command, args, url } = entry;
58
58
  if (typeof command !== "string" && typeof url === "string") {
59
- return isSharedServerUrl(url) ? { state: "shared_http", url } : { state: "different", current: url };
59
+ // Shown without user-info, query or fragment: a token in the URL must not
60
+ // reach the setup message or the prepare JSON (#295).
61
+ const shown = displayUrl(url);
62
+ if (shown === null)
63
+ return unreadable;
64
+ return isSharedServerUrl(url) ? { state: "shared_http", url: shown } : { state: "different", current: shown };
60
65
  }
61
66
  if (typeof command !== "string") {
62
- return { state: "different", current: JSON.stringify(entry) };
67
+ const transport = describeTransport(entry);
68
+ return transport === null ? unreadable : { state: "different", current: transport };
63
69
  }
64
70
  const argv = [command, ...(Array.isArray(args) ? args.map(String) : [])];
65
71
  const expected = serverCommand(platform);
@@ -68,6 +74,35 @@ export function classifyRegistration(entry, platform = process.platform) {
68
74
  }
69
75
  return { state: "different", current: argv.join(" ") };
70
76
  }
77
+ /**
78
+ * A URL with its user-info, query and fragment removed, which is where a
79
+ * token in a URL rides. Null when it doesn't parse.
80
+ */
81
+ function displayUrl(url) {
82
+ if (!URL.canParse(url))
83
+ return null;
84
+ const parsed = new URL(url);
85
+ parsed.username = "";
86
+ parsed.password = "";
87
+ parsed.search = "";
88
+ parsed.hash = "";
89
+ return parsed.href;
90
+ }
91
+ /**
92
+ * A commandless entry with no string `url` (Windsurf's `serverUrl`, Gemini's
93
+ * `httpUrl`), reduced to its transport label and its cleaned URL. Everything
94
+ * else (headers, env, credential helpers, unknown fields) can carry secrets,
95
+ * so it never prints.
96
+ */
97
+ function describeTransport(entry) {
98
+ const { type, serverUrl, httpUrl } = entry;
99
+ const raw = typeof serverUrl === "string" ? serverUrl : typeof httpUrl === "string" ? httpUrl : null;
100
+ const shown = raw === null ? null : displayUrl(raw);
101
+ if (shown === null)
102
+ return null;
103
+ const label = typeof type === "string" && /^[\w-]+$/.test(type) ? { type } : {};
104
+ return JSON.stringify({ ...label, url: shown });
105
+ }
71
106
  function readJsonFile(filePath) {
72
107
  try {
73
108
  return JSON.parse(fs.readFileSync(filePath, "utf-8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brightspace-mcp-server",
3
- "version": "5.0.9",
3
+ "version": "5.0.11",
4
4
  "mcpName": "io.github.RohanMuppa/brightspace",
5
5
  "description": "MCP server for Brightspace (D2L). Check grades, due dates, assignments, announcements, syllabus, rosters and more via Claude, ChatGPT, Cursor, Windsurf, or any MCP client.",
6
6
  "type": "module",