@frustrated/ms-graph-mcp 0.1.4
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/CONTRIBUTING.md +3 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/bun.lock +74 -0
- package/docs/jsr_mcp_ideation.md +83 -0
- package/docs/ms_graph_mcp_spec.md +212 -0
- package/docs/tools/README.md +13 -0
- package/docs/tools/calendar.md +31 -0
- package/docs/tools/mail.md +26 -0
- package/icon-512.png +0 -0
- package/package.json +45 -0
- package/src/auth.ts +165 -0
- package/src/config.ts +46 -0
- package/src/index.ts +80 -0
- package/src/mcp-interface.ts +122 -0
- package/src/tools/calendar.ts +41 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/mail.ts +34 -0
- package/src/utils.ts +20 -0
- package/tsconfig.json +21 -0
package/CONTRIBUTING.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Manus AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./icon-512.png" alt="Microsoft Graph MCP Icon" width="150"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Microsoft Graph MCP Package</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Seamless Microsoft Graph Integration for AI Agents.</b>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
A JSR-based TypeScript MCP (Model Context Protocol) package for personal Microsoft Graph access via CLI. This package enables AI agents to interact with personal Microsoft Graph APIs (Outlook, OneDrive, Calendar, etc.) through a local CLI interface.
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Local-First:** Prioritizes local execution and user data control.
|
|
18
|
+
- **Multi-Tenant Support:** Works with both personal Microsoft accounts and enterprise tenants.
|
|
19
|
+
- **Secure Authentication:** Implements OAuth 2.0 Authorization Code Flow with PKCE.
|
|
20
|
+
- **Secure Token Storage:** Uses OS-specific credential managers for token protection.
|
|
21
|
+
- **User Control:** Provides CLI commands for permission management and revocation.
|
|
22
|
+
- **MCP Standard:** Adheres to the Model Context Protocol for broad agent compatibility.
|
|
23
|
+
|
|
24
|
+
## Usage with Manus Agents
|
|
25
|
+
|
|
26
|
+
This package is designed to be integrated as a connection within the Manus UI, allowing AI agents to directly invoke its functionalities. It communicates via `stdio`, sending and receiving JSON payloads.
|
|
27
|
+
|
|
28
|
+
### Installation and Initialization
|
|
29
|
+
|
|
30
|
+
Before using the MCP CLI, you need to initialize it once to authenticate with your Microsoft account. This process will guide you through granting necessary permissions.
|
|
31
|
+
|
|
32
|
+
#### Using Bun (`bunx`)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bunx jsr:@frustrated/ms-graph-mcp init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Using Deno (`deno run`)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
deno run -A jsr:@frustrated/ms-graph-mcp init
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Using Node.js (`npx`)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx jsr @frustrated/ms-graph-mcp init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
These commands will:
|
|
51
|
+
1. Prompt you to authenticate with your Microsoft account (personal or organizational).
|
|
52
|
+
2. Open your browser to the Microsoft identity platform.
|
|
53
|
+
3. Grant consent for the requested scopes.
|
|
54
|
+
4. Securely store your refresh token locally using `keytar`.
|
|
55
|
+
|
|
56
|
+
### Running the MCP Server
|
|
57
|
+
|
|
58
|
+
Once initialized, Manus agents will typically run the MCP server to interact with Microsoft Graph. The server listens for JSON requests on `stdin` and outputs JSON responses to `stdout`.
|
|
59
|
+
|
|
60
|
+
#### Using Bun (`bunx`)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bunx jsr:@frustrated/ms-graph-mcp run
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Using Deno (`deno run`)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
deno run -A jsr:@frustrated/ms-graph-mcp run
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Using Node.js (`npx`)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx jsr @frustrated/ms-graph-mcp run
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Top-Level Tools
|
|
79
|
+
|
|
80
|
+
The Microsoft Graph MCP CLI exposes various top-level tools, each corresponding to a major Microsoft Graph service. AI agents can discover sub-tools within these categories as needed.
|
|
81
|
+
|
|
82
|
+
* **`mail`**: Manage email communications (e.g., list messages, send messages).
|
|
83
|
+
* **`calendar`**: Organize calendar events (e.g., create events, list events).
|
|
84
|
+
* **`onedrive`**: Interact with OneDrive files and folders (e.g., list files, upload files).
|
|
85
|
+
|
|
86
|
+
For detailed information on specific tools and their functionalities, refer to the [Tools Documentation](./docs/tools/README.md).
|
|
87
|
+
|
|
88
|
+
### Managing Permissions
|
|
89
|
+
|
|
90
|
+
To view the currently configured Client ID, Tenant ID, and enabled/disabled tools:
|
|
91
|
+
|
|
92
|
+
#### Using Bun (`bunx`)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
bunx jsr:@frustrated/ms-graph-mcp permissions
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Using Deno (`deno run`)
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
deno run -A jsr:@frustrated/ms-graph-mcp permissions
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Using Node.js (`npx`)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx jsr @frustrated/ms-graph-mcp permissions
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Revoking Access
|
|
111
|
+
|
|
112
|
+
To revoke the refresh token and disconnect the package from your Microsoft account:
|
|
113
|
+
|
|
114
|
+
#### Using Bun (`bunx`)
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bunx jsr:@frustrated/ms-graph-mcp revoke
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Using Deno (`deno run`)
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
deno run -A jsr:@frustrated/ms-graph-mcp revoke
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Using Node.js (`npx`)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx jsr @frustrated/ms-graph-mcp revoke
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Documentation
|
|
133
|
+
|
|
134
|
+
- [Ideation Document](./docs/ms_graph_mcp_ideation.md) - Conceptual design and motivation.
|
|
135
|
+
- [Technical Specification](./docs/ms_graph_mcp_spec.md) - Detailed implementation guide.
|
|
136
|
+
- [Tools Documentation](./docs/tools/README.md) - Comprehensive guide to all available MCP tools.
|
|
137
|
+
|
|
138
|
+
## Security Considerations
|
|
139
|
+
|
|
140
|
+
- Refresh tokens are stored using OS-specific credential managers (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux).
|
|
141
|
+
- All communication with Microsoft Graph is over HTTPS.
|
|
142
|
+
- Input validation is performed on all incoming MCP requests.
|
|
143
|
+
- Output from Microsoft Graph is sanitized before being passed to AI agents.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
Refer to the [CONTRIBUTING.md](./CONTRIBUTING.md) guide for details on how to contribute to this project.
|
package/bun.lock
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@frustrated/ms-graph-mcp",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@azure/msal-node": "latest",
|
|
9
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
10
|
+
"@microsoft/microsoft-graph-client": "^3.0.0",
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/node": "^20.10.0",
|
|
14
|
+
"bun-types": "latest",
|
|
15
|
+
"typescript": "^5.3.0",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"packages": {
|
|
20
|
+
"@azure/msal-common": ["@azure/msal-common@15.17.0", "", {}, "sha512-VQ5/gTLFADkwue+FohVuCqlzFPUq4xSrX8jeZe+iwZuY6moliNC8xt86qPVNYdtbQfELDf2Nu6LI+demFPHGgw=="],
|
|
21
|
+
|
|
22
|
+
"@azure/msal-node": ["@azure/msal-node@3.8.10", "", { "dependencies": { "@azure/msal-common": "15.17.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-0Hz7Kx4hs70KZWep/Rd7aw/qOLUF92wUOhn7ZsOuB5xNR/06NL1E2RAI9+UKH1FtvN8nD6mFjH7UKSjv6vOWvQ=="],
|
|
23
|
+
|
|
24
|
+
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
|
25
|
+
|
|
26
|
+
"@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="],
|
|
27
|
+
|
|
28
|
+
"@microsoft/microsoft-graph-client": ["@microsoft/microsoft-graph-client@3.0.7", "", { "dependencies": { "@babel/runtime": "^7.12.5", "tslib": "^2.2.0" } }, "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw=="],
|
|
29
|
+
|
|
30
|
+
"@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
|
|
31
|
+
|
|
32
|
+
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
|
33
|
+
|
|
34
|
+
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
|
35
|
+
|
|
36
|
+
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
|
37
|
+
|
|
38
|
+
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
|
39
|
+
|
|
40
|
+
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
|
|
41
|
+
|
|
42
|
+
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
|
43
|
+
|
|
44
|
+
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
|
45
|
+
|
|
46
|
+
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
|
|
47
|
+
|
|
48
|
+
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
|
|
49
|
+
|
|
50
|
+
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
|
|
51
|
+
|
|
52
|
+
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
|
|
53
|
+
|
|
54
|
+
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
|
|
55
|
+
|
|
56
|
+
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
|
|
57
|
+
|
|
58
|
+
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
|
59
|
+
|
|
60
|
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
|
61
|
+
|
|
62
|
+
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
|
63
|
+
|
|
64
|
+
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
|
65
|
+
|
|
66
|
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
|
67
|
+
|
|
68
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
69
|
+
|
|
70
|
+
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
|
71
|
+
|
|
72
|
+
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Ideation: JSR-Based TypeScript MCP Package for Personal Microsoft Graph Access
|
|
2
|
+
|
|
3
|
+
This document outlines a revised conceptual design for providing AI agents with access to personal Microsoft Graph APIs. The previous ideation focused on a hosted HTTP/SSE server. This revision pivots to a **non-HTTP, JSR-based TypeScript package** designed for local execution via `bunx` and `stdio`, addressing concerns about platform independence, cost, and potential competition from Microsoft.
|
|
4
|
+
|
|
5
|
+
## 1. Core Concept: Local, Portable, and User-Controlled
|
|
6
|
+
|
|
7
|
+
The core idea is to empower users with a self-hosted, easily runnable MCP solution for their personal Microsoft accounts. Instead of a centralized hosted service, this approach leverages modern JavaScript ecosystem tools to create a portable package that users can run on their local machines or in their preferred execution environments. This provides maximum control over data and permissions, mitigating concerns about vendor lock-in or service deprecation.
|
|
8
|
+
|
|
9
|
+
## 2. Architectural Framework: JSR, Bunx, and Stdio
|
|
10
|
+
|
|
11
|
+
The revised architecture centers around a TypeScript package distributed via JSR, executed by `bunx`, and communicating via standard input/output (stdio).
|
|
12
|
+
|
|
13
|
+
### 2.1 JSR (JavaScript Registry) for Distribution
|
|
14
|
+
|
|
15
|
+
JSR is a modern package registry optimized for TypeScript and compatible with various JavaScript runtimes, including Node.js, Deno, and Bun [1].
|
|
16
|
+
|
|
17
|
+
- **Benefits:**
|
|
18
|
+
- **Native TypeScript Support:** JSR natively understands TypeScript, eliminating the need for complex build configurations for package authors [1].
|
|
19
|
+
- **Universal Compatibility:** Packages published to JSR can be consumed by `npm`, `yarn`, `pnpm`, `bun`, and `deno` [1]. This ensures broad accessibility for users regardless of their preferred package manager or runtime.
|
|
20
|
+
- **ESM-first:** JSR promotes web-standard ECMAScript modules, aligning with modern JavaScript development practices [1].
|
|
21
|
+
|
|
22
|
+
### 2.2 Bunx for Execution
|
|
23
|
+
|
|
24
|
+
`bunx` is a lightweight CLI tool that comes with the Bun runtime. It allows users to execute npm packages and their binaries on demand without requiring global installation [2].
|
|
25
|
+
|
|
26
|
+
- **Benefits:**
|
|
27
|
+
- **Zero-Install Execution:** Users can run the MCP package directly using `bunx <package-name>`, simplifying the setup process significantly [2].
|
|
28
|
+
- **Performance:** Bun is known for its high performance, which can lead to faster startup times and execution of the MCP server logic [3].
|
|
29
|
+
- **TypeScript Support:** Bun has native TypeScript support, allowing direct execution of TypeScript files without a separate compilation step [4].
|
|
30
|
+
|
|
31
|
+
### 2.3 Stdio for Communication
|
|
32
|
+
|
|
33
|
+
Instead of HTTP/SSE, the MCP server will communicate with AI agents via standard input/output (stdio). This means the agent would spawn the MCP package as a child process and exchange messages over `stdin` and `stdout`.
|
|
34
|
+
|
|
35
|
+
- **Benefits:**
|
|
36
|
+
- **Local-First:** Ideal for local AI agents or development environments where direct process communication is simpler and more efficient than network calls.
|
|
37
|
+
- **Reduced Overhead:** Eliminates the need for network stacks, HTTP servers, and associated complexities, potentially reducing resource consumption.
|
|
38
|
+
- **Security:** Communication is confined to the local machine, reducing the attack surface compared to an exposed HTTP endpoint.
|
|
39
|
+
|
|
40
|
+
### Data Flow (Revised)
|
|
41
|
+
|
|
42
|
+
1. **User Setup:** User installs Bun and runs `bunx @your-org/ms-graph-mcp-package init` to configure their Microsoft application registration (Client ID, Tenant ID) and initiate the OAuth 2.0 Authorization Code Flow with PKCE. This step stores the refresh token securely on the local machine.
|
|
43
|
+
2. **Agent Integration:** The AI agent is configured to spawn the MCP package (e.g., `bunx @your-org/ms-graph-mcp-package run`) and communicate with it via `stdin`/`stdout`.
|
|
44
|
+
3. **Tool Execution:**
|
|
45
|
+
- The AI agent sends an MCP request (JSON) to the package's `stdin`.
|
|
46
|
+
- The package reads the request, retrieves the user's access token (refreshing if necessary), calls the Microsoft Graph API, and formats the response.
|
|
47
|
+
- The result (JSON) is written to the package's `stdout`, which the AI agent then reads.
|
|
48
|
+
|
|
49
|
+
## 3. Authentication Flow (Local Context)
|
|
50
|
+
|
|
51
|
+
The authentication flow remains OAuth 2.0 Authorization Code Flow with PKCE, but adapted for a local CLI context.
|
|
52
|
+
|
|
53
|
+
1. **Initial Authorization:** When the user runs `bunx @your-org/ms-graph-mcp-package init`:
|
|
54
|
+
- The package opens a browser window to the Microsoft identity platform authorization endpoint.
|
|
55
|
+
- The user logs in and grants consent for the requested scopes.
|
|
56
|
+
- The redirect URI will be a custom URI scheme (e.g., `ms-graph-mcp://auth-callback`) or a local web server that the package temporarily spins up to capture the authorization code.
|
|
57
|
+
- The package exchanges the authorization code for an access token and a refresh token.
|
|
58
|
+
2. **Token Storage:** The refresh token is stored securely on the local machine, potentially using OS-specific credential managers or encrypted files, to minimize exposure.
|
|
59
|
+
3. **Token Refresh:** When an access token expires, the package uses the stored refresh token to obtain a new access token silently.
|
|
60
|
+
|
|
61
|
+
## 4. Permission Transparency and User Control (Local Context)
|
|
62
|
+
|
|
63
|
+
While a web-based dashboard is no longer central, the principles of transparency and control can be maintained through CLI commands and local configuration.
|
|
64
|
+
|
|
65
|
+
- **CLI Commands:**
|
|
66
|
+
- `bunx @your-org/ms-graph-mcp-package permissions`: Displays currently granted scopes and their status.
|
|
67
|
+
- `bunx @your-org/ms-graph-mcp-package revoke`: Revokes the refresh token, disconnecting the package from the Microsoft account.
|
|
68
|
+
- `bunx @your-org/ms-graph-mcp-package audit`: Shows a local log of API calls made by the package (if logging is enabled).
|
|
69
|
+
- **Configuration File:** A local configuration file (e.g., `~/.config/ms-graph-mcp.json`) can allow users to disable specific tools or scope categories.
|
|
70
|
+
|
|
71
|
+
## 5. Technical Challenges and Considerations
|
|
72
|
+
|
|
73
|
+
- **Cross-Platform Credential Storage:** Securely storing refresh tokens across different operating systems (Windows, macOS, Linux) requires careful implementation using platform-specific APIs or well-vetted libraries.
|
|
74
|
+
- **User Experience for Auth:** The initial OAuth flow needs to be as smooth as possible, handling browser redirects and code exchange gracefully within a CLI context.
|
|
75
|
+
- **Error Handling:** Robust error handling for API calls, token refreshes, and stdio communication is crucial for a reliable user experience.
|
|
76
|
+
- **Tool Mapping:** The mapping of Microsoft Graph APIs to MCP tools will need to be well-defined and potentially configurable.
|
|
77
|
+
|
|
78
|
+
## References
|
|
79
|
+
|
|
80
|
+
[1] JSR. *JSR: the JavaScript Registry*. Available at: https://jsr.io/
|
|
81
|
+
[2] Bun. *bunx*. Available at: https://bun.com/docs/pm/bunx
|
|
82
|
+
[3] Bun. *Bun*. Available at: https://bun.com/
|
|
83
|
+
[4] Bun. *Install TypeScript declarations for Bun*. Available at: https://bun.com/docs/guides/runtime/typescript
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Technical Specification: JSR-Based Microsoft Graph MCP Package
|
|
2
|
+
|
|
3
|
+
This document provides a detailed technical specification for the implementation of a JSR-based TypeScript package designed to expose personal Microsoft Graph APIs via the Model Context Protocol (MCP). This package will operate as a local CLI tool, executed by `bunx`, and communicate with AI agents through standard input/output (stdio). The primary goal is to enable secure, user-controlled access to Microsoft Graph data for AI agents without relying on a hosted service.
|
|
4
|
+
|
|
5
|
+
## 1. Project Overview
|
|
6
|
+
|
|
7
|
+
### 1.1 Goal
|
|
8
|
+
To develop a robust, cross-platform, and user-friendly MCP package that allows AI agents to interact with personal Microsoft Graph APIs (Outlook, OneDrive, Calendar, etc.) through a local CLI interface, leveraging JSR for distribution and Bun for execution.
|
|
9
|
+
|
|
10
|
+
### 1.2 Key Principles
|
|
11
|
+
- **Local-First:** Prioritize local execution and data control.
|
|
12
|
+
- **Security:** Implement strong authentication and secure token storage.
|
|
13
|
+
- **User Control:** Provide clear mechanisms for permission management and revocation.
|
|
14
|
+
- **Simplicity:** Minimize setup complexity for end-users.
|
|
15
|
+
- **Interoperability:** Adhere to MCP standards for broad agent compatibility.
|
|
16
|
+
|
|
17
|
+
## 2. Project Structure
|
|
18
|
+
|
|
19
|
+
The project will follow a standard TypeScript package structure, distributed via JSR.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/ms-graph-mcp-package
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── index.ts # Main entry point for CLI commands (init, run, permissions, revoke)
|
|
25
|
+
│ ├── auth.ts # Handles OAuth 2.0 flow and token management
|
|
26
|
+
│ ├── config.ts # Manages local configuration (app registration details, tool permissions)
|
|
27
|
+
│ ├── tools/ # Directory for individual MCP tool implementations
|
|
28
|
+
│ │ ├── mail.ts # MCP tools for Outlook Mail API
|
|
29
|
+
│ │ ├── calendar.ts # MCP tools for Outlook Calendar API
|
|
30
|
+
│ │ ├── onedrive.ts # MCP tools for OneDrive API
|
|
31
|
+
│ │ └── ... # Other Microsoft Graph service tools
|
|
32
|
+
│ ├── mcp-interface.ts # Defines MCP message formats and stdio communication logic
|
|
33
|
+
│ └── utils.ts # Utility functions (logging, error handling, platform-specific helpers)
|
|
34
|
+
├── package.json
|
|
35
|
+
├── tsconfig.json
|
|
36
|
+
├── jsr.json # JSR specific configuration
|
|
37
|
+
├── README.md
|
|
38
|
+
└── .env.example # Example environment variables
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 3. Dependencies
|
|
42
|
+
|
|
43
|
+
Core dependencies will include:
|
|
44
|
+
|
|
45
|
+
- `@microsoft/microsoft-graph-client`: Official Microsoft Graph SDK for Node.js (compatible with Bun).
|
|
46
|
+
- `msal-node`: Microsoft Authentication Library for Node.js, for handling OAuth 2.0.
|
|
47
|
+
- `commander`: For building the CLI interface.
|
|
48
|
+
- `keytar` (or similar): For secure, cross-platform credential storage.
|
|
49
|
+
- `zod` (or similar): For schema validation of MCP messages and configuration.
|
|
50
|
+
- `bun` (as dev dependency for types and runtime).
|
|
51
|
+
|
|
52
|
+
## 4. Authentication Flow
|
|
53
|
+
|
|
54
|
+
The package will implement the OAuth 2.0 Authorization Code Flow with PKCE, adapted for a CLI environment.
|
|
55
|
+
|
|
56
|
+
### 4.1 Centralized Application Registration
|
|
57
|
+
The package is designed to work with a **single, pre-registered Multi-tenant application** in the Microsoft Entra ID portal. This application is configured to support:
|
|
58
|
+
- **Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)**
|
|
59
|
+
- **Personal Microsoft accounts (e.g. Skype, Xbox)**
|
|
60
|
+
|
|
61
|
+
**Pre-configured Application Details:**
|
|
62
|
+
- **Application (Client) ID:** `[PRE_REGISTERED_CLIENT_ID]`
|
|
63
|
+
- **Tenant ID:** `common` (This endpoint allows both organizational and personal accounts to sign in [1]).
|
|
64
|
+
- **Redirect URI:** `http://localhost:PORT/auth-callback` (A dynamic local web server will be started by the CLI to capture the authorization code).
|
|
65
|
+
|
|
66
|
+
### 4.2 `init` Command (`bunx @org/package init`)
|
|
67
|
+
This command initiates the simplified authentication process:
|
|
68
|
+
1. Displays a message explaining the authentication process and the scopes being requested.
|
|
69
|
+
2. Constructs the authorization URL using the centralized Client ID and the `common` tenant endpoint.
|
|
70
|
+
3. Starts a temporary local web server on a random available port to listen for the redirect.
|
|
71
|
+
4. Opens the authorization URL in the user's default web browser.
|
|
72
|
+
5. The user signs in with their personal or organizational account and grants consent.
|
|
73
|
+
6. Upon redirect to `localhost`, the CLI captures the authorization code.
|
|
74
|
+
7. Exchanges the authorization code for an access token and a refresh token using `msal-node` and the PKCE challenge.
|
|
75
|
+
8. Securely stores the refresh token using `keytar` or an encrypted file.
|
|
76
|
+
9. Stores the centralized Client ID and the user's tenant information (if applicable) in a local configuration file.
|
|
77
|
+
|
|
78
|
+
**Note for Advanced Users:** The CLI should provide an option (e.g., `--custom-app`) to override the centralized Client ID with their own application registration for maximum privacy and control.
|
|
79
|
+
|
|
80
|
+
### 4.3 Token Refresh
|
|
81
|
+
- The `auth.ts` module will handle automatic token refreshing using the stored refresh token. This should occur transparently before any Microsoft Graph API call if the current access token is expired or near expiration.
|
|
82
|
+
|
|
83
|
+
## 5. Token Management
|
|
84
|
+
|
|
85
|
+
### 5.1 Secure Storage
|
|
86
|
+
Refresh tokens are highly sensitive and must be stored securely.
|
|
87
|
+
- **Primary Method:** Utilize `keytar` for OS-specific credential storage (macOS Keychain, Windows Credential Manager, Linux Secret Service). This is the most secure option for local applications.
|
|
88
|
+
- **Fallback Method:** If `keytar` is unavailable or fails, fall back to an encrypted file storage. The encryption key could be derived from a user-provided passphrase or an OS-generated key, ensuring the file is not readable without proper authorization.
|
|
89
|
+
|
|
90
|
+
### 5.2 Scope Management
|
|
91
|
+
- The `init` command will request a base set of common scopes. Users can manually add or remove scopes in their Azure App Registration. The package will respect the scopes granted to the application.
|
|
92
|
+
|
|
93
|
+
## 6. MCP Tool Definitions
|
|
94
|
+
|
|
95
|
+
Each Microsoft Graph API will be exposed as one or more MCP tools. Tools will be defined with clear input and output schemas using `zod` for validation.
|
|
96
|
+
|
|
97
|
+
### 6.1 Example: `mail.list_messages`
|
|
98
|
+
|
|
99
|
+
- **Description:** Lists mail messages from the user's inbox or specified folder.
|
|
100
|
+
- **Input Schema:**
|
|
101
|
+
```typescript
|
|
102
|
+
interface ListMailMessagesInput {
|
|
103
|
+
folderId?: string; // Optional: ID of the mail folder (e.g., 'inbox', 'sentitems')
|
|
104
|
+
top?: number; // Optional: Number of messages to retrieve (default: 10)
|
|
105
|
+
filter?: string; // Optional: OData filter string
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
- **Output Schema:**
|
|
109
|
+
```typescript
|
|
110
|
+
interface ListMailMessagesOutput {
|
|
111
|
+
messages: Array<{ // Array of simplified mail message objects
|
|
112
|
+
id: string;
|
|
113
|
+
subject: string;
|
|
114
|
+
from: { name: string; address: string };
|
|
115
|
+
receivedDateTime: string;
|
|
116
|
+
isRead: boolean;
|
|
117
|
+
bodyPreview: string;
|
|
118
|
+
webLink: string; // Link to the message in Outlook Web App
|
|
119
|
+
}>;
|
|
120
|
+
nextLink?: string; // OData nextLink for pagination
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 6.2 Example: `calendar.create_event`
|
|
125
|
+
|
|
126
|
+
- **Description:** Creates a new calendar event.
|
|
127
|
+
- **Input Schema:**
|
|
128
|
+
```typescript
|
|
129
|
+
interface CreateCalendarEventInput {
|
|
130
|
+
subject: string;
|
|
131
|
+
start: { dateTime: string; timeZone: string };
|
|
132
|
+
end: { dateTime: string; timeZone: string };
|
|
133
|
+
content?: string; // Optional: Body content of the event
|
|
134
|
+
attendees?: Array<{ emailAddress: string; type: 'required' | 'optional' }>;
|
|
135
|
+
location?: string;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
- **Output Schema:**
|
|
139
|
+
```typescript
|
|
140
|
+
interface CreateCalendarEventOutput {
|
|
141
|
+
id: string;
|
|
142
|
+
webLink: string; // Link to the event in Outlook Web App
|
|
143
|
+
status: 'created' | 'failed';
|
|
144
|
+
errorMessage?: string;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 7. Communication Protocol (Stdio)
|
|
149
|
+
|
|
150
|
+
The package will communicate with AI agents by reading JSON messages from `stdin` and writing JSON responses to `stdout`.
|
|
151
|
+
|
|
152
|
+
### 7.1 Request Format (from Agent to MCP Package)
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"type": "request",
|
|
157
|
+
"id": "<unique-request-id>",
|
|
158
|
+
"tool": "<tool-name>",
|
|
159
|
+
"input": { /* tool-specific input parameters */ }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 7.2 Response Format (from MCP Package to Agent)
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"type": "response",
|
|
168
|
+
"id": "<unique-request-id>",
|
|
169
|
+
"status": "success" | "error",
|
|
170
|
+
"output": { /* tool-specific output data */ },
|
|
171
|
+
"error": { "code": "<code>", "message": "<message>" } // Only if status is "error"
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## 8. Error Handling
|
|
176
|
+
|
|
177
|
+
- **Graph API Errors:** Translate Microsoft Graph API errors into a standardized MCP error format, including status codes and messages.
|
|
178
|
+
- **Validation Errors:** Use `zod` to validate incoming MCP requests. Return clear error messages for invalid inputs.
|
|
179
|
+
- **Authentication Errors:** Handle token expiration, invalid refresh tokens, and insufficient permissions gracefully, prompting the user for re-authentication if necessary.
|
|
180
|
+
|
|
181
|
+
## 9. Configuration and User Control
|
|
182
|
+
|
|
183
|
+
### 9.1 Configuration File
|
|
184
|
+
A local JSON configuration file (e.g., `~/.config/ms-graph-mcp/config.json`) will store:
|
|
185
|
+
- `clientId`: The Microsoft Entra Application (Client) ID.
|
|
186
|
+
- `tenantId`: The Microsoft Entra Tenant ID.
|
|
187
|
+
- `enabledTools`: An array of strings or a map to enable/disable specific MCP tools or categories.
|
|
188
|
+
|
|
189
|
+
### 9.2 CLI Commands for User Control
|
|
190
|
+
- `bunx @org/package permissions`: Displays the configured Client ID, Tenant ID, and a list of currently enabled/disabled tools.
|
|
191
|
+
- `bunx @org/package revoke`: Revokes the refresh token from local storage and optionally from Microsoft Entra ID (if supported by MSAL), effectively disconnecting the package.
|
|
192
|
+
- `bunx @org/package audit`: (Optional, future enhancement) Displays a local log of tool invocations.
|
|
193
|
+
|
|
194
|
+
## 10. Security Considerations
|
|
195
|
+
|
|
196
|
+
- **Refresh Token Protection:** As detailed in Section 5.1, refresh tokens must be stored using the most secure method available for the operating system.
|
|
197
|
+
- **Least Privilege:** Encourage users to grant only the necessary Microsoft Graph scopes to their registered application.
|
|
198
|
+
- **Input Validation:** Strictly validate all incoming MCP requests to prevent injection attacks or unexpected behavior.
|
|
199
|
+
- **Output Sanitization:** Ensure that any data returned from Microsoft Graph is properly sanitized before being passed back to the AI agent, especially if the agent might display it to the user.
|
|
200
|
+
|
|
201
|
+
## 11. References
|
|
202
|
+
|
|
203
|
+
[1] JSR. *JSR: the JavaScript Registry*. Available at: https://jsr.io/
|
|
204
|
+
[2] Bun. *bunx*. Available at: https://bun.com/docs/pm/bunx
|
|
205
|
+
[3] Bun. *Bun*. Available at: https://bun.com/
|
|
206
|
+
[4] Bun. *Install TypeScript declarations for Bun*. Available at: https://bun.com/docs/guides/runtime/typescript
|
|
207
|
+
[5] Microsoft. *Microsoft Graph SDK for JavaScript*. Available at: https://learn.microsoft.com/en-us/graph/sdks/sdk-overview#javascript-sdk
|
|
208
|
+
[6] Microsoft. *Microsoft Authentication Library for Node.js (MSAL Node)*. Available at: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-node
|
|
209
|
+
[7] Commander. *Commander.js*. Available at: https://github.com/tj/commander.js
|
|
210
|
+
[8] Keytar. *keytar*. Available at: https://github.com/atom/node-keytar
|
|
211
|
+
[9] Zod. *Zod*. Available at: https://zod.dev/
|
|
212
|
+
[10] Microsoft. *Getting Microsoft entra oauth login to work with personal accounts*. Available at: https://learn.microsoft.com/en-us/answers/questions/1525326/getting-microsoft-entra-oauth-login-to-work-with-p
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Microsoft Graph MCP Tools Documentation
|
|
2
|
+
|
|
3
|
+
This document provides an overview of the top-level tools available in the Microsoft Graph MCP CLI. Each top-level tool corresponds to a major Microsoft Graph service and contains a set of sub-tools for specific operations.
|
|
4
|
+
|
|
5
|
+
## Top-Level Tools
|
|
6
|
+
|
|
7
|
+
* **[Mail](./mail.md)**: Interact with email messages, folders, and other mail-related functionalities.
|
|
8
|
+
* **[Calendar](./calendar.md)**: Manage calendar events, schedules, and attendees.
|
|
9
|
+
* **OneDrive (Coming Soon)**: Interact with files and folders in OneDrive.
|
|
10
|
+
|
|
11
|
+
## How to Discover Sub-Tools
|
|
12
|
+
|
|
13
|
+
AI agents can discover the specific sub-tools available within each top-level tool by referring to their respective documentation files (e.g., `mail.md` for mail-related operations). When interacting with the MCP CLI, agents can specify the full tool path (e.g., `mail.list_messages`) to invoke a particular function.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Calendar Tools
|
|
2
|
+
|
|
3
|
+
This document details the sub-tools available under the `calendar` top-level tool. These tools allow interaction with the user's Microsoft 365 calendar.
|
|
4
|
+
|
|
5
|
+
## `calendar.create_event`
|
|
6
|
+
|
|
7
|
+
Creates a new event in the user's calendar.
|
|
8
|
+
|
|
9
|
+
### Parameters
|
|
10
|
+
|
|
11
|
+
* `subject` (string, required): The subject of the event.
|
|
12
|
+
* `start` (object, required): The start date and time of the event.
|
|
13
|
+
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T09:00:00`).
|
|
14
|
+
* `timeZone` (string, required): The time zone of the start time (e.g., `America/Los_Angeles`).
|
|
15
|
+
* `end` (object, required): The end date and time of the event.
|
|
16
|
+
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T10:00:00`).
|
|
17
|
+
* `timeZone` (string, required): The time zone of the end time (e.g., `America/Los_Angeles`).
|
|
18
|
+
* `content` (string, optional): The body content of the event, in HTML format.
|
|
19
|
+
* `attendees` (array of objects, optional): A list of attendees for the event.
|
|
20
|
+
* Each object contains:
|
|
21
|
+
* `emailAddress` (string, required): The email address of the attendee.
|
|
22
|
+
* `type` (string, required): The attendee type, either `required` or `optional`.
|
|
23
|
+
* `location` (string, optional): The display name of the event location.
|
|
24
|
+
|
|
25
|
+
### Returns
|
|
26
|
+
|
|
27
|
+
An object containing:
|
|
28
|
+
* `id` (string): The unique identifier of the created event.
|
|
29
|
+
* `webLink` (string): A URL to open the event in Outlook Web App.
|
|
30
|
+
* `status` (string): The status of the event creation, either `created` or `failed`.
|
|
31
|
+
* `errorMessage` (string, optional): An error message if the event creation failed.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Mail Tools
|
|
2
|
+
|
|
3
|
+
This document details the sub-tools available under the `mail` top-level tool. These tools allow interaction with the user's Microsoft 365 mailbox.
|
|
4
|
+
|
|
5
|
+
## `mail.list_messages`
|
|
6
|
+
|
|
7
|
+
Lists messages from the user's mailbox.
|
|
8
|
+
|
|
9
|
+
### Parameters
|
|
10
|
+
|
|
11
|
+
* `folderId` (string, optional): The ID of the mail folder to retrieve messages from. If not provided, defaults to the inbox (`/me/messages`).
|
|
12
|
+
* `top` (number, optional): The maximum number of messages to return in the response.
|
|
13
|
+
* `filter` (string, optional): An OData filter query to apply to the messages (e.g., `isRead eq false`).
|
|
14
|
+
|
|
15
|
+
### Returns
|
|
16
|
+
|
|
17
|
+
An object containing:
|
|
18
|
+
* `messages` (array): A list of message objects, each containing:
|
|
19
|
+
* `id` (string): The unique identifier of the message.
|
|
20
|
+
* `subject` (string): The subject of the message.
|
|
21
|
+
* `from` (object): Information about the sender.
|
|
22
|
+
* `receivedDateTime` (string): The date and time the message was received.
|
|
23
|
+
* `isRead` (boolean): Indicates whether the message has been read.
|
|
24
|
+
* `bodyPreview` (string): A short preview of the message body.
|
|
25
|
+
* `webLink` (string): A URL to open the message in Outlook Web App.
|
|
26
|
+
* `nextLink` (string, optional): A URL to retrieve the next page of messages, if available.
|
package/icon-512.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@frustrated/ms-graph-mcp",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "A JSR-based TypeScript MCP package for personal Microsoft Graph access via CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ms-graph-mcp": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run src/index.ts",
|
|
15
|
+
"test": "bun test",
|
|
16
|
+
"typecheck": "bun run tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"nodeModulesDir": "auto",
|
|
19
|
+
"imports": {
|
|
20
|
+
"@azure/msal-node": "npm:@azure/msal-node@^3.0.0",
|
|
21
|
+
"@microsoft/microsoft-graph-client": "npm:@microsoft/microsoft-graph-client@^3.0.0",
|
|
22
|
+
"@commander-js/extra-typings": "npm:@commander-js/extra-typings@^14.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@azure/msal-node": "^3.0.0",
|
|
26
|
+
"@microsoft/microsoft-graph-client": "^3.0.0",
|
|
27
|
+
"@commander-js/extra-typings": "^14.0.0"
|
|
28
|
+
},
|
|
29
|
+
"publish": {
|
|
30
|
+
"include": [
|
|
31
|
+
"src/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/usually-frustrated/ms-graph-mcp.git"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"bun-types": "latest",
|
|
42
|
+
"typescript": "^5.3.0",
|
|
43
|
+
"@types/node": "^20.10.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { PublicClientApplication, Configuration, LogLevel, CryptoProvider } from '@azure/msal-node';
|
|
2
|
+
import { AddressInfo } from 'node:net';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import { createServer } from 'node:http';
|
|
7
|
+
|
|
8
|
+
const MSAL_CONFIG: Configuration = {
|
|
9
|
+
auth: {
|
|
10
|
+
clientId: process.env.MS_GRAPH_CLIENT_ID || 'YOUR_CENTRALIZED_CLIENT_ID_HERE', // Replace with actual Client ID
|
|
11
|
+
authority: 'https://login.microsoftonline.com/common',
|
|
12
|
+
},
|
|
13
|
+
system: {
|
|
14
|
+
loggerOptions: {
|
|
15
|
+
loggerCallback(loglevel, message, containsPii) {
|
|
16
|
+
console.log(message);
|
|
17
|
+
},
|
|
18
|
+
piiLoggingEnabled: false,
|
|
19
|
+
logLevel: LogLevel.Verbose,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const REDIRECT_URI_PATH = '/auth-callback';
|
|
25
|
+
const TOKEN_CACHE_FILE = path.join(os.homedir(), '.config', 'ms-graph-mcp', 'msal_cache.json');
|
|
26
|
+
const SCOPES = ['User.Read', 'Mail.ReadWrite', 'Calendars.ReadWrite', 'Files.ReadWrite.All', 'offline_access'];
|
|
27
|
+
|
|
28
|
+
const pca = new PublicClientApplication(MSAL_CONFIG);
|
|
29
|
+
|
|
30
|
+
async function saveTokenCache() {
|
|
31
|
+
const serialized = pca.getTokenCache().serialize();
|
|
32
|
+
await fs.mkdir(path.dirname(TOKEN_CACHE_FILE), { recursive: true });
|
|
33
|
+
await fs.writeFile(TOKEN_CACHE_FILE, serialized, { mode: 0o600 });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function loadTokenCache(): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
const data = await fs.readFile(TOKEN_CACHE_FILE, 'utf-8');
|
|
39
|
+
pca.getTokenCache().deserialize(data);
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function initAuth(): Promise<void> {
|
|
47
|
+
console.log('Initiating Microsoft Graph authentication...');
|
|
48
|
+
console.log('Please ensure you have registered a multi-tenant application in Azure AD with the following redirect URI: http://localhost:PORT/auth-callback');
|
|
49
|
+
|
|
50
|
+
const cryptoProvider = new CryptoProvider();
|
|
51
|
+
const pkceCodes = await cryptoProvider.generatePkceCodes();
|
|
52
|
+
|
|
53
|
+
const authCodeUrlParameters = {
|
|
54
|
+
scopes: SCOPES,
|
|
55
|
+
redirectUri: `http://localhost:0${REDIRECT_URI_PATH}`,
|
|
56
|
+
codeChallenge: pkceCodes.challenge,
|
|
57
|
+
codeChallengeMethod: 'S256' as const,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const authCodeUrl = await pca.getAuthCodeUrl(authCodeUrlParameters);
|
|
61
|
+
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const server = createServer(async (req, res) => {
|
|
64
|
+
if (req.url?.startsWith(REDIRECT_URI_PATH)) {
|
|
65
|
+
const url = new URL(`http://localhost${req.url}`);
|
|
66
|
+
const code = url.searchParams.get('code');
|
|
67
|
+
|
|
68
|
+
if (code) {
|
|
69
|
+
try {
|
|
70
|
+
const addr = server.address() as AddressInfo;
|
|
71
|
+
const tokenResult = await pca.acquireTokenByCode({
|
|
72
|
+
code,
|
|
73
|
+
scopes: SCOPES,
|
|
74
|
+
redirectUri: `http://localhost:${addr.port}${REDIRECT_URI_PATH}`,
|
|
75
|
+
codeVerifier: pkceCodes.verifier,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (tokenResult) {
|
|
79
|
+
await saveTokenCache();
|
|
80
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
81
|
+
res.end('Authentication successful! You can close this window.');
|
|
82
|
+
console.log('Authentication successful. Tokens saved securely.');
|
|
83
|
+
server.close(() => resolve());
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error('Authentication failed: no token result received.');
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Error acquiring token:', error);
|
|
89
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
90
|
+
res.end('Authentication failed. Check console for details.');
|
|
91
|
+
server.close(() => reject(error));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
95
|
+
res.end('Authorization code not found in redirect.');
|
|
96
|
+
server.close(() => reject(new Error('Authorization code not found.')));
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
100
|
+
res.end('Not Found');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
server.listen(0, () => {
|
|
105
|
+
const addr = server.address() as AddressInfo;
|
|
106
|
+
const finalRedirectUri = `http://localhost:${addr.port}${REDIRECT_URI_PATH}`;
|
|
107
|
+
const finalAuthUrl = authCodeUrl.replace(`http://localhost:0${REDIRECT_URI_PATH}`, finalRedirectUri);
|
|
108
|
+
console.log(`\nOpen this URL in your browser to authenticate:\n\n ${finalAuthUrl}\n`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
server.on('error', (err) => {
|
|
112
|
+
console.error('Server error:', err);
|
|
113
|
+
reject(err);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function getAccessToken(): Promise<string> {
|
|
119
|
+
await loadTokenCache();
|
|
120
|
+
|
|
121
|
+
const accounts = await pca.getAllAccounts();
|
|
122
|
+
if (accounts.length === 0) {
|
|
123
|
+
throw new Error('No account found. Please run `ms-graph-mcp init` first.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const tokenResult = await pca.acquireTokenSilent({
|
|
128
|
+
account: accounts[0],
|
|
129
|
+
scopes: SCOPES,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!tokenResult) {
|
|
133
|
+
throw new Error('Failed to acquire access token silently.');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await saveTokenCache();
|
|
137
|
+
return tokenResult.accessToken;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Error refreshing token:', error);
|
|
140
|
+
await fs.unlink(TOKEN_CACHE_FILE).catch(() => {});
|
|
141
|
+
throw new Error('Failed to refresh access token. Please re-authenticate using `ms-graph-mcp init`.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function revokeAuth(): Promise<void> {
|
|
146
|
+
try {
|
|
147
|
+
await fs.unlink(TOKEN_CACHE_FILE).catch(() => {});
|
|
148
|
+
console.log('Authentication revoked. All tokens cleared.');
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Error revoking authentication:', error);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function getAuthStatus(): Promise<{ clientId: string; tenantId: string; isAuthenticated: boolean }> {
|
|
156
|
+
await loadTokenCache();
|
|
157
|
+
const accounts = await pca.getAllAccounts();
|
|
158
|
+
const isAuthenticated = accounts.length > 0;
|
|
159
|
+
const authority = MSAL_CONFIG.auth.authority ?? 'https://login.microsoftonline.com/common';
|
|
160
|
+
return {
|
|
161
|
+
clientId: MSAL_CONFIG.auth.clientId,
|
|
162
|
+
tenantId: authority.split('/').pop() || 'common',
|
|
163
|
+
isAuthenticated,
|
|
164
|
+
};
|
|
165
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ms-graph-mcp');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
interface AppConfig {
|
|
9
|
+
clientId: string;
|
|
10
|
+
tenantId: string;
|
|
11
|
+
enabledTools: string[]; // List of tool names that are enabled
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let appConfig: AppConfig = {
|
|
15
|
+
clientId: process.env.MS_GRAPH_CLIENT_ID || 'YOUR_CENTRALIZED_CLIENT_ID_HERE', // Default centralized client ID
|
|
16
|
+
tenantId: 'common',
|
|
17
|
+
enabledTools: [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function loadConfig(): Promise<AppConfig> {
|
|
21
|
+
try {
|
|
22
|
+
const configContent = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
23
|
+
appConfig = { ...appConfig, ...JSON.parse(configContent) };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// If file doesn't exist or is invalid, use default config
|
|
26
|
+
console.warn('No existing config found or config file is invalid. Using default configuration.');
|
|
27
|
+
}
|
|
28
|
+
return appConfig;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function saveConfig(newConfig: Partial<AppConfig>): Promise<void> {
|
|
32
|
+
appConfig = { ...appConfig, ...newConfig };
|
|
33
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
34
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(appConfig, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getConfig(): AppConfig {
|
|
38
|
+
return appConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isToolEnabled(toolName: string): boolean {
|
|
42
|
+
return appConfig.enabledTools.includes(toolName);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Initialize config on module load
|
|
46
|
+
loadConfig();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "@commander-js/extra-typings";
|
|
3
|
+
import { initAuth, revokeAuth, getAuthStatus } from "./auth.ts";
|
|
4
|
+
import { getConfig, saveConfig } from "./config.ts";
|
|
5
|
+
import { startMcpServer } from "./mcp-interface.ts";
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name("ms-graph-mcp")
|
|
11
|
+
.description("CLI for JSR-based Microsoft Graph MCP package")
|
|
12
|
+
.version("0.1.0");
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command("init")
|
|
16
|
+
.description("Initialize authentication with Microsoft Graph")
|
|
17
|
+
.action(async () => {
|
|
18
|
+
try {
|
|
19
|
+
await initAuth();
|
|
20
|
+
console.log("Authentication process completed.");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Authentication failed:", error instanceof Error ? error.message : String(error));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("revoke")
|
|
29
|
+
.description("Revoke Microsoft Graph authentication and clear tokens")
|
|
30
|
+
.action(async () => {
|
|
31
|
+
try {
|
|
32
|
+
await revokeAuth();
|
|
33
|
+
console.log("Authentication revoked successfully.");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("Failed to revoke authentication:", error instanceof Error ? error.message : String(error));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command("permissions")
|
|
42
|
+
.description(
|
|
43
|
+
"Display current authentication status and configured permissions",
|
|
44
|
+
)
|
|
45
|
+
.action(async () => {
|
|
46
|
+
try {
|
|
47
|
+
const authStatus = await getAuthStatus();
|
|
48
|
+
const config = getConfig();
|
|
49
|
+
console.log("--- Microsoft Graph MCP Status ---");
|
|
50
|
+
console.log(`Client ID: ${authStatus.clientId}`);
|
|
51
|
+
console.log(`Tenant ID: ${authStatus.tenantId}`);
|
|
52
|
+
console.log(
|
|
53
|
+
`Authenticated: ${authStatus.isAuthenticated ? "Yes" : "No"}`,
|
|
54
|
+
);
|
|
55
|
+
console.log("\nEnabled Tools:");
|
|
56
|
+
if (config.enabledTools.length > 0) {
|
|
57
|
+
config.enabledTools.forEach((tool) => console.log(`- ${tool}`));
|
|
58
|
+
} else {
|
|
59
|
+
console.log(
|
|
60
|
+
"No specific tools are explicitly enabled in config. All available tools will be used.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
console.log("----------------------------------");
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Failed to retrieve status:", error instanceof Error ? error.message : String(error));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command("run")
|
|
72
|
+
.description("Start the MCP server to listen for commands via stdin/stdout")
|
|
73
|
+
.action(async () => {
|
|
74
|
+
console.log("MCP server started. Listening for commands on stdin...");
|
|
75
|
+
// Placeholder for MCP interface logic
|
|
76
|
+
// This will be implemented in src/mcp-interface.ts and called here
|
|
77
|
+
startMcpServer();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getAccessToken } from './auth.ts';
|
|
2
|
+
import { getConfig, isToolEnabled } from './config.ts';
|
|
3
|
+
import { error, log } from './utils.ts';
|
|
4
|
+
import { Client } from '@microsoft/microsoft-graph-client';
|
|
5
|
+
|
|
6
|
+
// Define MCP message interfaces
|
|
7
|
+
interface McpRequest {
|
|
8
|
+
type: 'request';
|
|
9
|
+
id: string;
|
|
10
|
+
tool: string;
|
|
11
|
+
input: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface McpResponse {
|
|
15
|
+
type: 'response';
|
|
16
|
+
id: string;
|
|
17
|
+
status: 'success' | 'error';
|
|
18
|
+
output?: any;
|
|
19
|
+
error?: { code: string; message: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Placeholder for registered tools
|
|
23
|
+
import { tools as registeredTools } from './tools/index.ts';
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async function processMcpRequest(request: McpRequest): Promise<McpResponse> {
|
|
27
|
+
const config = getConfig();
|
|
28
|
+
|
|
29
|
+
if (!isToolEnabled(request.tool)) {
|
|
30
|
+
return {
|
|
31
|
+
type: 'response',
|
|
32
|
+
id: request.id,
|
|
33
|
+
status: 'error',
|
|
34
|
+
error: { code: 'TOOL_DISABLED', message: `Tool '${request.tool}' is disabled.` },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const toolHandler = (registeredTools as Record<string, ((graphClient: Client, input: any) => Promise<any>) | undefined>)[request.tool];
|
|
39
|
+
if (!toolHandler) {
|
|
40
|
+
return {
|
|
41
|
+
type: 'response',
|
|
42
|
+
id: request.id,
|
|
43
|
+
status: 'error',
|
|
44
|
+
error: { code: 'TOOL_NOT_FOUND', message: `Tool '${request.tool}' not found.` },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const accessToken = await getAccessToken();
|
|
50
|
+
const graphClient = Client.init({
|
|
51
|
+
authProvider: (done) => {
|
|
52
|
+
done(null, accessToken);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const output = await toolHandler(graphClient, request.input);
|
|
57
|
+
return {
|
|
58
|
+
type: 'response',
|
|
59
|
+
id: request.id,
|
|
60
|
+
status: 'success',
|
|
61
|
+
output,
|
|
62
|
+
};
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
error(`Error executing tool '${request.tool}':`, err);
|
|
65
|
+
return {
|
|
66
|
+
type: 'response',
|
|
67
|
+
id: request.id,
|
|
68
|
+
status: 'error',
|
|
69
|
+
error: { code: err.code || 'TOOL_EXECUTION_ERROR', message: err.message || 'An unknown error occurred.' },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function startMcpServer() {
|
|
75
|
+
log('MCP server started. Listening for commands on stdin...');
|
|
76
|
+
|
|
77
|
+
process.stdin.setEncoding('utf8');
|
|
78
|
+
|
|
79
|
+
let buffer = '';
|
|
80
|
+
process.stdin.on('data', async (chunk) => {
|
|
81
|
+
buffer += chunk;
|
|
82
|
+
let newlineIndex;
|
|
83
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
84
|
+
const line = buffer.substring(0, newlineIndex).trim();
|
|
85
|
+
buffer = buffer.substring(newlineIndex + 1);
|
|
86
|
+
|
|
87
|
+
if (line) {
|
|
88
|
+
try {
|
|
89
|
+
const request: McpRequest = JSON.parse(line);
|
|
90
|
+
if (request.type === 'request' && request.id && request.tool) {
|
|
91
|
+
const response = await processMcpRequest(request);
|
|
92
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
93
|
+
} else {
|
|
94
|
+
error('Invalid MCP request format:', new Error(line));
|
|
95
|
+
process.stdout.write(JSON.stringify({
|
|
96
|
+
type: 'response',
|
|
97
|
+
id: request.id || 'unknown',
|
|
98
|
+
status: 'error',
|
|
99
|
+
error: { code: 'INVALID_REQUEST', message: 'Invalid MCP request format.' },
|
|
100
|
+
}) + '\n');
|
|
101
|
+
}
|
|
102
|
+
} catch (parseError: any) {
|
|
103
|
+
error('Failed to parse stdin input as JSON:', parseError);
|
|
104
|
+
process.stdout.write(JSON.stringify({
|
|
105
|
+
type: 'response',
|
|
106
|
+
id: 'unknown',
|
|
107
|
+
status: 'error',
|
|
108
|
+
error: { code: 'JSON_PARSE_ERROR', message: 'Invalid JSON input.' },
|
|
109
|
+
}) + '\n');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
process.stdin.on('end', () => {
|
|
116
|
+
log('Stdin closed. MCP server shutting down.');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
process.stdin.on('error', (err) => {
|
|
120
|
+
error('Stdin error:', err);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Client } from '@microsoft/microsoft-graph-client';
|
|
2
|
+
import { log, error } from '../utils.ts';
|
|
3
|
+
|
|
4
|
+
export async function createEvent(graphClient: Client, input: {
|
|
5
|
+
subject: string;
|
|
6
|
+
start: { dateTime: string; timeZone: string };
|
|
7
|
+
end: { dateTime: string; timeZone: string };
|
|
8
|
+
content?: string;
|
|
9
|
+
attendees?: Array<{ emailAddress: string; type: 'required' | 'optional' }>;
|
|
10
|
+
location?: string;
|
|
11
|
+
}): Promise<any> {
|
|
12
|
+
try {
|
|
13
|
+
const event = {
|
|
14
|
+
subject: input.subject,
|
|
15
|
+
start: input.start,
|
|
16
|
+
end: input.end,
|
|
17
|
+
body: input.content ? { contentType: 'HTML', content: input.content } : undefined,
|
|
18
|
+
attendees: input.attendees?.map(att => ({
|
|
19
|
+
emailAddress: { address: att.emailAddress },
|
|
20
|
+
type: att.type,
|
|
21
|
+
})),
|
|
22
|
+
location: input.location ? { displayName: input.location } : undefined,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const response = await graphClient.api('/me/events').post(event);
|
|
26
|
+
log(`Created calendar event: ${response.subject} (ID: ${response.id})`);
|
|
27
|
+
return {
|
|
28
|
+
id: response.id,
|
|
29
|
+
webLink: response.webLink,
|
|
30
|
+
status: 'created',
|
|
31
|
+
};
|
|
32
|
+
} catch (err: any) {
|
|
33
|
+
error('Error creating calendar event:', err);
|
|
34
|
+
return {
|
|
35
|
+
id: null,
|
|
36
|
+
webLink: null,
|
|
37
|
+
status: 'failed',
|
|
38
|
+
errorMessage: err.message,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as mail from './mail.ts';
|
|
2
|
+
import * as calendar from './calendar.ts';
|
|
3
|
+
// import * as onedrive from './onedrive'; // Placeholder for future tools
|
|
4
|
+
|
|
5
|
+
export const tools = {
|
|
6
|
+
'mail.list_messages': mail.listMessages,
|
|
7
|
+
'calendar.create_event': calendar.createEvent,
|
|
8
|
+
// 'onedrive.list_files': onedrive.listFiles,
|
|
9
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Client } from '@microsoft/microsoft-graph-client';
|
|
2
|
+
import { log, error } from '../utils.ts';
|
|
3
|
+
|
|
4
|
+
export async function listMessages(graphClient: Client, input: { folderId?: string; top?: number; filter?: string }): Promise<any> {
|
|
5
|
+
try {
|
|
6
|
+
let request = graphClient.api(input.folderId ? `/me/mailFolders/${input.folderId}/messages` :
|
|
7
|
+
'/me/messages');
|
|
8
|
+
|
|
9
|
+
if (input.top) {
|
|
10
|
+
request = request.top(input.top);
|
|
11
|
+
}
|
|
12
|
+
if (input.filter) {
|
|
13
|
+
request = request.filter(input.filter);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const response = await request.select('id,subject,from,receivedDateTime,isRead,bodyPreview,webLink').get();
|
|
17
|
+
log(`Listed ${response.value.length} messages.`);
|
|
18
|
+
return {
|
|
19
|
+
messages: response.value.map((msg: any) => ({
|
|
20
|
+
id: msg.id,
|
|
21
|
+
subject: msg.subject,
|
|
22
|
+
from: msg.from,
|
|
23
|
+
receivedDateTime: msg.receivedDateTime,
|
|
24
|
+
isRead: msg.isRead,
|
|
25
|
+
bodyPreview: msg.bodyPreview,
|
|
26
|
+
webLink: msg.webLink,
|
|
27
|
+
})),
|
|
28
|
+
nextLink: response['@odata.nextLink'],
|
|
29
|
+
};
|
|
30
|
+
} catch (err: any) {
|
|
31
|
+
error('Error listing messages:', err);
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function log(message: string, level: 'info' | 'warn' | 'error' = 'info') {
|
|
5
|
+
const timestamp = new Date().toISOString();
|
|
6
|
+
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function error(message: string, err?: Error) {
|
|
10
|
+
log(message, 'error');
|
|
11
|
+
if (err) {
|
|
12
|
+
console.error(err);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function warn(message: string) {
|
|
17
|
+
log(message, 'warn');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Placeholder for future file-based logging or other utilities
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ESNext", "DOM"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"allowImportingTsExtensions": true,
|
|
17
|
+
"noEmit": true,
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist"],
|
|
21
|
+
}
|