@softeria/ms-365-mcp-server 0.22.1 → 0.24.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/README.md CHANGED
@@ -19,6 +19,70 @@ API.
19
19
  - Read-only mode support for safe operations
20
20
  - Tool filtering for granular access control
21
21
 
22
+ ## Output Format: JSON vs TOON
23
+
24
+ The server supports two output formats that can be configured globally:
25
+
26
+ ### JSON Format (Default)
27
+
28
+ Standard JSON output with pretty-printing:
29
+
30
+ ```json
31
+ {
32
+ "value": [
33
+ {
34
+ "id": "1",
35
+ "displayName": "Alice Johnson",
36
+ "mail": "alice@example.com",
37
+ "jobTitle": "Software Engineer"
38
+ }
39
+ ]
40
+ }
41
+ ```
42
+
43
+ ### (experimental) TOON Format
44
+
45
+ [Token-Oriented Object Notation](https://github.com/toon-format/toon) for efficient LLM token usage:
46
+
47
+ ```
48
+ value[1]{id,displayName,mail,jobTitle}:
49
+ "1",Alice Johnson,alice@example.com,Software Engineer
50
+ ```
51
+
52
+ **Benefits:**
53
+
54
+ - 30-60% fewer tokens vs JSON
55
+ - Best for uniform array data (lists of emails, calendar events, files, etc.)
56
+ - Ideal for cost-sensitive applications at scale
57
+
58
+ **Usage:**
59
+ (experimental) Enable TOON format globally:
60
+
61
+ Via CLI flag:
62
+
63
+ ```bash
64
+ npx @softeria/ms-365-mcp-server --toon
65
+ ```
66
+
67
+ Via Claude Desktop configuration:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "ms365": {
73
+ "command": "npx",
74
+ "args": ["-y", "@softeria/ms-365-mcp-server", "--toon"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ Via environment variable:
81
+
82
+ ```bash
83
+ MS365_MCP_OUTPUT_FORMAT=toon npx @softeria/ms-365-mcp-server
84
+ ```
85
+
22
86
  ## Supported Services & Tools
23
87
 
24
88
  ### Personal Account Tools (Available by default)
@@ -289,6 +353,7 @@ When running as an MCP server, the following options can be used:
289
353
  Starts Express.js server with MCP endpoint at /mcp
290
354
  --enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
291
355
  --enabled-tools <pattern> Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)
356
+ --toon (experimental) Enable TOON output format for 30-60% token reduction
292
357
  ```
293
358
 
294
359
  Environment variables:
@@ -297,6 +362,7 @@ Environment variables:
297
362
  - `ENABLED_TOOLS`: Filter tools using a regex pattern (alternative to --enabled-tools flag)
298
363
  - `MS365_MCP_ORG_MODE=true|1`: Enable organization/work mode (alternative to --org-mode flag)
299
364
  - `MS365_MCP_FORCE_WORK_SCOPES=true|1`: Backwards compatibility for MS365_MCP_ORG_MODE
365
+ - `MS365_MCP_OUTPUT_FORMAT=toon`: Enable TOON output format (alternative to --toon flag)
300
366
  - `LOG_LEVEL`: Set logging level (default: 'info')
301
367
  - `SILENT=true|1`: Disable console output
302
368
  - `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
@@ -27,6 +27,12 @@ export function generateMcpTools(openApiSpec, outputDir) {
27
27
 
28
28
  let clientCode = fs.readFileSync(clientFilePath, 'utf-8');
29
29
  clientCode = clientCode.replace(/'@zodios\/core';/, "'./hack.js';");
30
+
31
+ clientCode = clientCode.replace(
32
+ /const microsoft_graph_attachment = z\s+\.object\({[\s\S]*?}\)\s+\.strict\(\);/,
33
+ (match) => match.replace(/\.strict\(\);/, '.passthrough();')
34
+ );
35
+
30
36
  fs.writeFileSync(clientFilePath, clientCode);
31
37
 
32
38
  return true;
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
19
19
  ).option(
20
20
  "--org-mode",
21
21
  "Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
22
- ).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)");
22
+ ).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");
23
23
  function parseArgs() {
24
24
  program.parse();
25
25
  const options = program.opts();
@@ -38,6 +38,9 @@ function parseArgs() {
38
38
  if (options.workMode || options.forceWorkScopes) {
39
39
  options.orgMode = true;
40
40
  }
41
+ if (process.env.MS365_MCP_OUTPUT_FORMAT === "toon") {
42
+ options.toon = true;
43
+ }
41
44
  return options;
42
45
  }
43
46
  export {
@@ -1198,7 +1198,7 @@ const microsoft_graph_attachment = z.object({
1198
1198
  ).nullish(),
1199
1199
  name: z.string().describe("The attachment's file name.").nullish(),
1200
1200
  size: z.number().gte(-2147483648).lte(2147483647).describe("The length of the attachment in bytes.").optional()
1201
- }).strict();
1201
+ }).passthrough();
1202
1202
  const microsoft_graph_attendeeType = z.enum(["required", "optional", "resource"]);
1203
1203
  const microsoft_graph_dateTimeTimeZone = z.object({
1204
1204
  dateTime: z.string().describe(
@@ -1,10 +1,13 @@
1
1
  import logger from "./logger.js";
2
2
  import { refreshAccessToken } from "./lib/microsoft-auth.js";
3
+ import { encode as toonEncode } from "@toon-format/toon";
3
4
  class GraphClient {
4
- constructor(authManager) {
5
+ constructor(authManager, outputFormat = "json") {
5
6
  this.accessToken = null;
6
7
  this.refreshToken = null;
8
+ this.outputFormat = "json";
7
9
  this.authManager = authManager;
10
+ this.outputFormat = outputFormat;
8
11
  }
9
12
  setOAuthTokens(accessToken, refreshToken) {
10
13
  this.accessToken = accessToken;
@@ -94,6 +97,17 @@ class GraphClient {
94
97
  body: options.body
95
98
  });
96
99
  }
100
+ serializeData(data, outputFormat, pretty = false) {
101
+ if (outputFormat === "toon") {
102
+ try {
103
+ return toonEncode(data);
104
+ } catch (error) {
105
+ logger.warn(`Failed to encode as TOON, falling back to JSON: ${error}`);
106
+ return JSON.stringify(data, null, pretty ? 2 : void 0);
107
+ }
108
+ }
109
+ return JSON.stringify(data, null, pretty ? 2 : void 0);
110
+ }
97
111
  async graphRequest(endpoint, options = {}) {
98
112
  try {
99
113
  logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
@@ -110,7 +124,7 @@ class GraphClient {
110
124
  formatJsonResponse(data, rawResponse = false, excludeResponse = false) {
111
125
  if (excludeResponse) {
112
126
  return {
113
- content: [{ type: "text", text: JSON.stringify({ success: true }) }]
127
+ content: [{ type: "text", text: this.serializeData({ success: true }, this.outputFormat) }]
114
128
  };
115
129
  }
116
130
  if (data && typeof data === "object" && "_headers" in data) {
@@ -124,13 +138,17 @@ class GraphClient {
124
138
  }
125
139
  if (rawResponse) {
126
140
  return {
127
- content: [{ type: "text", text: JSON.stringify(responseData.data) }],
141
+ content: [
142
+ { type: "text", text: this.serializeData(responseData.data, this.outputFormat) }
143
+ ],
128
144
  _meta: meta
129
145
  };
130
146
  }
131
147
  if (responseData.data === null || responseData.data === void 0) {
132
148
  return {
133
- content: [{ type: "text", text: JSON.stringify({ success: true }) }],
149
+ content: [
150
+ { type: "text", text: this.serializeData({ success: true }, this.outputFormat) }
151
+ ],
134
152
  _meta: meta
135
153
  };
136
154
  }
@@ -147,18 +165,20 @@ class GraphClient {
147
165
  };
148
166
  removeODataProps2(responseData.data);
149
167
  return {
150
- content: [{ type: "text", text: JSON.stringify(responseData.data, null, 2) }],
168
+ content: [
169
+ { type: "text", text: this.serializeData(responseData.data, this.outputFormat, true) }
170
+ ],
151
171
  _meta: meta
152
172
  };
153
173
  }
154
174
  if (rawResponse) {
155
175
  return {
156
- content: [{ type: "text", text: JSON.stringify(data) }]
176
+ content: [{ type: "text", text: this.serializeData(data, this.outputFormat) }]
157
177
  };
158
178
  }
159
179
  if (data === null || data === void 0) {
160
180
  return {
161
- content: [{ type: "text", text: JSON.stringify({ success: true }) }]
181
+ content: [{ type: "text", text: this.serializeData({ success: true }, this.outputFormat) }]
162
182
  };
163
183
  }
164
184
  const removeODataProps = (obj) => {
@@ -174,7 +194,7 @@ class GraphClient {
174
194
  };
175
195
  removeODataProps(data);
176
196
  return {
177
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
197
+ content: [{ type: "text", text: this.serializeData(data, this.outputFormat, true) }]
178
198
  };
179
199
  }
180
200
  }
package/dist/server.js CHANGED
@@ -8,6 +8,7 @@ import logger, { enableConsoleLogging } from "./logger.js";
8
8
  import { registerAuthTools } from "./auth-tools.js";
9
9
  import { registerGraphTools } from "./graph-tools.js";
10
10
  import GraphClient from "./graph-client.js";
11
+ import { buildScopesFromEndpoints } from "./auth.js";
11
12
  import { MicrosoftOAuthProvider } from "./oauth-provider.js";
12
13
  import {
13
14
  exchangeCodeForToken,
@@ -19,7 +20,8 @@ class MicrosoftGraphServer {
19
20
  constructor(authManager, options = {}) {
20
21
  this.authManager = authManager;
21
22
  this.options = options;
22
- this.graphClient = new GraphClient(authManager);
23
+ const outputFormat = options.toon ? "toon" : "json";
24
+ this.graphClient = new GraphClient(authManager, outputFormat);
23
25
  this.server = null;
24
26
  }
25
27
  async initialize(version) {
@@ -56,6 +58,7 @@ class MicrosoftGraphServer {
56
58
  if (this.options.http) {
57
59
  const port = typeof this.options.http === "string" ? parseInt(this.options.http) : 3e3;
58
60
  const app = express();
61
+ app.set("trust proxy", true);
59
62
  app.use(express.json());
60
63
  app.use(express.urlencoded({ extended: true }));
61
64
  app.use((req, res, next) => {
@@ -73,7 +76,9 @@ class MicrosoftGraphServer {
73
76
  });
74
77
  const oauthProvider = new MicrosoftOAuthProvider(this.authManager);
75
78
  app.get("/.well-known/oauth-authorization-server", async (req, res) => {
76
- const url = new URL(`${req.protocol}://${req.get("host")}`);
79
+ const protocol = req.secure ? "https" : "http";
80
+ const url = new URL(`${protocol}://${req.get("host")}`);
81
+ const scopes = buildScopesFromEndpoints(this.options.orgMode);
77
82
  res.json({
78
83
  issuer: url.origin,
79
84
  authorization_endpoint: `${url.origin}/authorize`,
@@ -84,15 +89,17 @@ class MicrosoftGraphServer {
84
89
  grant_types_supported: ["authorization_code", "refresh_token"],
85
90
  token_endpoint_auth_methods_supported: ["none"],
86
91
  code_challenge_methods_supported: ["S256"],
87
- scopes_supported: ["User.Read", "Files.Read", "Mail.Read"]
92
+ scopes_supported: scopes
88
93
  });
89
94
  });
90
95
  app.get("/.well-known/oauth-protected-resource", async (req, res) => {
91
- const url = new URL(`${req.protocol}://${req.get("host")}`);
96
+ const protocol = req.secure ? "https" : "http";
97
+ const url = new URL(`${protocol}://${req.get("host")}`);
98
+ const scopes = buildScopesFromEndpoints(this.options.orgMode);
92
99
  res.json({
93
100
  resource: `${url.origin}/mcp`,
94
101
  authorization_servers: [url.origin],
95
- scopes_supported: ["User.Read", "Files.Read", "Mail.Read"],
102
+ scopes_supported: scopes,
96
103
  bearer_methods_supported: ["header"],
97
104
  resource_documentation: `${url.origin}`
98
105
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
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",
@@ -35,6 +35,7 @@
35
35
  "dependencies": {
36
36
  "@azure/msal-node": "^2.1.0",
37
37
  "@modelcontextprotocol/sdk": "^1.8.0",
38
+ "@toon-format/toon": "^0.8.0",
38
39
  "commander": "^11.1.0",
39
40
  "dotenv": "^17.0.1",
40
41
  "express": "^5.1.0",
@@ -55,6 +56,7 @@
55
56
  "@typescript-eslint/parser": "^8.38.0",
56
57
  "eslint": "^9.31.0",
57
58
  "globals": "^16.3.0",
59
+ "patch-package": "^8.0.1",
58
60
  "prettier": "^3.5.3",
59
61
  "semantic-release": "^24.2.7",
60
62
  "tsup": "^8.5.0",