@softeria/ms-365-mcp-server 0.30.1 → 0.31.1

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/Dockerfile CHANGED
@@ -1,9 +1,9 @@
1
- FROM node:20-alpine AS builder
1
+ FROM node:24-alpine AS builder
2
2
 
3
3
  WORKDIR /app
4
4
 
5
5
  COPY package*.json ./
6
- RUN npm ci --ignore-scripts
6
+ RUN npm i
7
7
 
8
8
  COPY . .
9
9
  RUN npm run generate
@@ -17,6 +17,6 @@ COPY --from=builder /app/dist /app/dist
17
17
  COPY --from=builder /app/package*.json ./
18
18
 
19
19
  ENV NODE_ENV=production
20
- RUN npm ci --ignore-scripts --omit=dev
20
+ RUN npm i --ignore-scripts --omit=dev
21
21
 
22
22
  ENTRYPOINT ["node", "dist/index.js"]
package/README.md CHANGED
@@ -269,6 +269,40 @@ claude mcp add ms365-china -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server
269
269
  For other interfaces that support MCPs, please refer to their respective documentation for the correct
270
270
  integration method.
271
271
 
272
+ ### Open WebUI
273
+
274
+ Open WebUI supports MCP servers via HTTP transport with OAuth 2.1.
275
+
276
+ 1. Start the server with HTTP mode and dynamic registration enabled:
277
+
278
+ ```bash
279
+ npx @softeria/ms-365-mcp-server --http --enable-dynamic-registration
280
+ ```
281
+
282
+ 2. In Open WebUI, go to **Admin Settings → Tools** (`/admin/settings/tools`) → **Add Connection**:
283
+ - **Type**: MCP Streamable HTTP
284
+ - **URL**: Your MCP server URL with `/mcp` path
285
+ - **Auth**: OAuth 2.1
286
+
287
+ 3. Click **Register Client**.
288
+
289
+ > **Note**: The `--enable-dynamic-registration` is required for Open WebUI to work. If using a custom Azure Entra app, add your redirect URI under "Mobile and desktop applications" platform (not "Single-page application").
290
+
291
+ **Quick test setup** using the default Azure app (ID `ms-365` and `localhost:8080` are pre-configured):
292
+
293
+ ```bash
294
+ docker run -d -p 8080:8080 \
295
+ -e WEBUI_AUTH=false \
296
+ -e OPENAI_API_KEY \
297
+ ghcr.io/open-webui/open-webui:main
298
+
299
+ npx @softeria/ms-365-mcp-server --http --enable-dynamic-registration
300
+ ```
301
+
302
+ Then add connection with URL `http://localhost:3000/mcp` and ID `ms-365`.
303
+
304
+ ![Open WebUI MCP Connection](https://github.com/user-attachments/assets/dcab71dd-cf02-4bcb-b7db-5725d6be4064)
305
+
272
306
  ### Local Development
273
307
 
274
308
  For local development or testing:
@@ -434,6 +468,7 @@ When running as an MCP server, the following options can be used:
434
468
  --http [port] Use Streamable HTTP transport instead of stdio (optionally specify port, default: 3000)
435
469
  Starts Express.js server with MCP endpoint at /mcp
436
470
  --enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
471
+ --enable-dynamic-registration Enable OAuth Dynamic Client Registration endpoint (required for Open WebUI)
437
472
  --enabled-tools <pattern> Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)
438
473
  --preset <names> Use preset tool categories (comma-separated). See "Tool Presets" section above
439
474
  --list-presets List all available presets and exit
@@ -552,4 +587,4 @@ If you're having problems or need help:
552
587
 
553
588
  ## License
554
589
 
555
- MIT © 2025 Softeria
590
+ MIT © 2026 Softeria
package/dist/auth.js CHANGED
@@ -80,9 +80,8 @@ function buildScopesFromEndpoints(includeWorkAccountScopes = false, enabledTools
80
80
  }
81
81
  });
82
82
  Object.entries(SCOPE_HIERARCHY).forEach(([higherScope, lowerScopes]) => {
83
- if (lowerScopes.every((scope) => scopesSet.has(scope))) {
83
+ if (scopesSet.has(higherScope) && lowerScopes.every((scope) => scopesSet.has(scope))) {
84
84
  lowerScopes.forEach((scope) => scopesSet.delete(scope));
85
- scopesSet.add(higherScope);
86
85
  }
87
86
  });
88
87
  const scopes = Array.from(scopesSet);
package/dist/cli.js CHANGED
@@ -23,7 +23,10 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
23
23
  ).option("--list-presets", "List all available presets and exit").option(
24
24
  "--org-mode",
25
25
  "Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
26
- ).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option("--cloud <type>", "Microsoft cloud environment: global (default) or china (21Vianet)");
26
+ ).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option("--cloud <type>", "Microsoft cloud environment: global (default) or china (21Vianet)").option(
27
+ "--enable-dynamic-registration",
28
+ "Enable OAuth Dynamic Client Registration endpoint (required for some MCP clients like Open WebUI)"
29
+ );
27
30
  function parseArgs() {
28
31
  program.parse();
29
32
  const options = program.opts();
@@ -45,7 +45,9 @@ async function executeGraphTool(tool, config, graphClient, params) {
45
45
  path2 = path2.replace(`{${paramName}}`, encodeURIComponent(paramValue)).replace(`:${paramName}`, encodeURIComponent(paramValue));
46
46
  break;
47
47
  case "Query":
48
- queryParams[fixedParamName] = `${paramValue}`;
48
+ if (paramValue !== "" && paramValue != null) {
49
+ queryParams[fixedParamName] = `${paramValue}`;
50
+ }
49
51
  break;
50
52
  case "Body":
51
53
  if (paramDef.schema) {
package/dist/secrets.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import logger from "./logger.js";
2
- import { parseCloudType } from "./cloud-config.js";
2
+ import { parseCloudType, getDefaultClientId } from "./cloud-config.js";
3
3
  class EnvironmentSecretsProvider {
4
4
  async getSecrets() {
5
+ const cloudType = parseCloudType(process.env.MS365_MCP_CLOUD_TYPE);
5
6
  return {
6
- clientId: process.env.MS365_MCP_CLIENT_ID || "",
7
+ clientId: process.env.MS365_MCP_CLIENT_ID || getDefaultClientId(cloudType),
7
8
  tenantId: process.env.MS365_MCP_TENANT_ID || "common",
8
9
  clientSecret: process.env.MS365_MCP_CLIENT_SECRET,
9
- cloudType: parseCloudType(process.env.MS365_MCP_CLOUD_TYPE)
10
+ cloudType
10
11
  };
11
12
  }
12
13
  }
package/dist/server.js CHANGED
@@ -108,7 +108,7 @@ class MicrosoftGraphServer {
108
108
  const protocol = req.secure ? "https" : "http";
109
109
  const url = new URL(`${protocol}://${req.get("host")}`);
110
110
  const scopes = buildScopesFromEndpoints(this.options.orgMode, this.options.enabledTools);
111
- res.json({
111
+ const metadata = {
112
112
  issuer: url.origin,
113
113
  authorization_endpoint: `${url.origin}/authorize`,
114
114
  token_endpoint: `${url.origin}/token`,
@@ -118,7 +118,11 @@ class MicrosoftGraphServer {
118
118
  token_endpoint_auth_methods_supported: ["none"],
119
119
  code_challenge_methods_supported: ["S256"],
120
120
  scopes_supported: scopes
121
- });
121
+ };
122
+ if (this.options.enableDynamicRegistration) {
123
+ metadata.registration_endpoint = `${url.origin}/register`;
124
+ }
125
+ res.json(metadata);
122
126
  });
123
127
  app.get("/.well-known/oauth-protected-resource", async (req, res) => {
124
128
  const protocol = req.secure ? "https" : "http";
@@ -132,6 +136,22 @@ class MicrosoftGraphServer {
132
136
  resource_documentation: `${url.origin}`
133
137
  });
134
138
  });
139
+ if (this.options.enableDynamicRegistration) {
140
+ app.post("/register", async (req, res) => {
141
+ const body = req.body;
142
+ logger.info("Client registration request", { body });
143
+ const clientId = `mcp-client-${Date.now()}`;
144
+ res.status(201).json({
145
+ client_id: clientId,
146
+ client_id_issued_at: Math.floor(Date.now() / 1e3),
147
+ redirect_uris: body.redirect_uris || [],
148
+ grant_types: body.grant_types || ["authorization_code", "refresh_token"],
149
+ response_types: body.response_types || ["code"],
150
+ token_endpoint_auth_method: body.token_endpoint_auth_method || "none",
151
+ client_name: body.client_name || "MCP Client"
152
+ });
153
+ });
154
+ }
135
155
  app.get("/authorize", async (req, res) => {
136
156
  const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
137
157
  const tenantId = this.secrets?.tenantId || "common";
@@ -193,11 +213,14 @@ class MicrosoftGraphServer {
193
213
  const tenantId = this.secrets?.tenantId || "common";
194
214
  const clientId = this.secrets.clientId;
195
215
  const clientSecret = this.secrets?.clientSecret;
196
- if (clientSecret) {
197
- logger.info("Token endpoint: Using confidential client with client_secret");
198
- } else {
199
- logger.info("Token endpoint: Using public client without client_secret");
200
- }
216
+ logger.info("Token endpoint: authorization_code exchange", {
217
+ redirect_uri: body.redirect_uri,
218
+ has_code: !!body.code,
219
+ has_code_verifier: !!body.code_verifier,
220
+ clientId,
221
+ tenantId,
222
+ hasClientSecret: !!clientSecret
223
+ });
201
224
  const result = await exchangeCodeForToken(
202
225
  body.code,
203
226
  body.redirect_uri,
package/glama.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": ["eirikb"]
4
+ }
@@ -1,5 +1,5 @@
1
- 2026-01-19 11:31:37 INFO: Using environment variables for secrets
2
- 2026-01-19 11:31:37 INFO: Using environment variables for secrets
3
- 2026-01-19 11:31:37 INFO: Using environment variables for secrets
4
- 2026-01-19 11:31:37 INFO: Using environment variables for secrets
5
- 2026-01-19 11:31:37 INFO: Using environment variables for secrets
1
+ 2026-01-27 20:03:08 INFO: Using environment variables for secrets
2
+ 2026-01-27 20:03:08 INFO: Using environment variables for secrets
3
+ 2026-01-27 20:03:08 INFO: Using environment variables for secrets
4
+ 2026-01-27 20:03:08 INFO: Using environment variables for secrets
5
+ 2026-01-27 20:03:08 INFO: Using environment variables for secrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.30.1",
3
+ "version": "0.31.1",
4
4
  "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "@semantic-release/exec": "^7.1.0",
54
54
  "@semantic-release/git": "^10.0.1",
55
55
  "@semantic-release/github": "^11.0.3",
56
- "@semantic-release/npm": "^12.0.2",
56
+ "@semantic-release/npm": "^13.1.3",
57
57
  "@types/express": "^5.0.3",
58
58
  "@types/node": "^22.15.15",
59
59
  "@typescript-eslint/eslint-plugin": "^8.38.0",
@@ -63,7 +63,7 @@
63
63
  "globals": "^16.3.0",
64
64
  "patch-package": "^8.0.1",
65
65
  "prettier": "^3.5.3",
66
- "semantic-release": "^24.2.7",
66
+ "semantic-release": "^25.0.2",
67
67
  "tsup": "^8.5.0",
68
68
  "tsx": "^4.19.4",
69
69
  "typescript": "^5.8.3",