@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 +66 -0
- package/bin/modules/generate-mcp-tools.mjs +6 -0
- package/dist/cli.js +4 -1
- package/dist/generated/client.js +1 -1
- package/dist/graph-client.js +28 -8
- package/dist/server.js +12 -5
- package/package.json +3 -1
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 {
|
package/dist/generated/client.js
CHANGED
|
@@ -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
|
-
}).
|
|
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(
|
package/dist/graph-client.js
CHANGED
|
@@ -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:
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
92
|
+
scopes_supported: scopes
|
|
88
93
|
});
|
|
89
94
|
});
|
|
90
95
|
app.get("/.well-known/oauth-protected-resource", async (req, res) => {
|
|
91
|
-
const
|
|
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:
|
|
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.
|
|
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",
|