@awolve/myoffice 1.6.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.
Files changed (94) hide show
  1. package/README.md +271 -0
  2. package/dist/auth/cache-plugin.d.ts +20 -0
  3. package/dist/auth/cache-plugin.d.ts.map +1 -0
  4. package/dist/auth/cache-plugin.js +49 -0
  5. package/dist/auth/cache-plugin.js.map +1 -0
  6. package/dist/auth/config.d.ts +40 -0
  7. package/dist/auth/config.d.ts.map +1 -0
  8. package/dist/auth/config.js +135 -0
  9. package/dist/auth/config.js.map +1 -0
  10. package/dist/auth/device-code.d.ts +2 -0
  11. package/dist/auth/device-code.d.ts.map +1 -0
  12. package/dist/auth/device-code.js +40 -0
  13. package/dist/auth/device-code.js.map +1 -0
  14. package/dist/auth/index.d.ts +4 -0
  15. package/dist/auth/index.d.ts.map +1 -0
  16. package/dist/auth/index.js +4 -0
  17. package/dist/auth/index.js.map +1 -0
  18. package/dist/auth/login.d.ts +7 -0
  19. package/dist/auth/login.d.ts.map +1 -0
  20. package/dist/auth/login.js +22 -0
  21. package/dist/auth/login.js.map +1 -0
  22. package/dist/auth/token-manager.d.ts +4 -0
  23. package/dist/auth/token-manager.d.ts.map +1 -0
  24. package/dist/auth/token-manager.js +85 -0
  25. package/dist/auth/token-manager.js.map +1 -0
  26. package/dist/cli/formatter.d.ts +5 -0
  27. package/dist/cli/formatter.d.ts.map +1 -0
  28. package/dist/cli/formatter.js +317 -0
  29. package/dist/cli/formatter.js.map +1 -0
  30. package/dist/cli.d.ts +3 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +973 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/core/handler.d.ts +8 -0
  35. package/dist/core/handler.d.ts.map +1 -0
  36. package/dist/core/handler.js +327 -0
  37. package/dist/core/handler.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +924 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/tools/calendar.d.ts +124 -0
  43. package/dist/tools/calendar.d.ts.map +1 -0
  44. package/dist/tools/calendar.js +129 -0
  45. package/dist/tools/calendar.js.map +1 -0
  46. package/dist/tools/chats.d.ts +66 -0
  47. package/dist/tools/chats.d.ts.map +1 -0
  48. package/dist/tools/chats.js +102 -0
  49. package/dist/tools/chats.js.map +1 -0
  50. package/dist/tools/contacts.d.ts +138 -0
  51. package/dist/tools/contacts.d.ts.map +1 -0
  52. package/dist/tools/contacts.js +189 -0
  53. package/dist/tools/contacts.js.map +1 -0
  54. package/dist/tools/index.d.ts +10 -0
  55. package/dist/tools/index.d.ts.map +1 -0
  56. package/dist/tools/index.js +10 -0
  57. package/dist/tools/index.js.map +1 -0
  58. package/dist/tools/mail.d.ts +138 -0
  59. package/dist/tools/mail.d.ts.map +1 -0
  60. package/dist/tools/mail.js +187 -0
  61. package/dist/tools/mail.js.map +1 -0
  62. package/dist/tools/onedrive.d.ts +125 -0
  63. package/dist/tools/onedrive.d.ts.map +1 -0
  64. package/dist/tools/onedrive.js +203 -0
  65. package/dist/tools/onedrive.js.map +1 -0
  66. package/dist/tools/planner.d.ts +390 -0
  67. package/dist/tools/planner.d.ts.map +1 -0
  68. package/dist/tools/planner.js +693 -0
  69. package/dist/tools/planner.js.map +1 -0
  70. package/dist/tools/sharepoint.d.ts +138 -0
  71. package/dist/tools/sharepoint.d.ts.map +1 -0
  72. package/dist/tools/sharepoint.js +156 -0
  73. package/dist/tools/sharepoint.js.map +1 -0
  74. package/dist/tools/tasks.d.ts +107 -0
  75. package/dist/tools/tasks.d.ts.map +1 -0
  76. package/dist/tools/tasks.js +131 -0
  77. package/dist/tools/tasks.js.map +1 -0
  78. package/dist/tools/teams.d.ts +66 -0
  79. package/dist/tools/teams.d.ts.map +1 -0
  80. package/dist/tools/teams.js +69 -0
  81. package/dist/tools/teams.js.map +1 -0
  82. package/dist/utils/graph-client.d.ts +15 -0
  83. package/dist/utils/graph-client.d.ts.map +1 -0
  84. package/dist/utils/graph-client.js +126 -0
  85. package/dist/utils/graph-client.js.map +1 -0
  86. package/dist/utils/signature.d.ts +2 -0
  87. package/dist/utils/signature.d.ts.map +1 -0
  88. package/dist/utils/signature.js +17 -0
  89. package/dist/utils/signature.js.map +1 -0
  90. package/dist/utils/version.d.ts +2 -0
  91. package/dist/utils/version.d.ts.map +1 -0
  92. package/dist/utils/version.js +20 -0
  93. package/dist/utils/version.js.map +1 -0
  94. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # MyOffice MCP
2
+
3
+ A lightweight MCP (Model Context Protocol) server and CLI for personal Microsoft 365 access, designed for AI assistants like Claude Code.
4
+
5
+ Unlike admin-focused M365 tools, this uses **delegated authentication** - users authenticate as themselves and can only access their own data.
6
+
7
+ ## Features
8
+
9
+ - **Email** - List, read, search, send, reply, delete, mark read/unread
10
+ - **Calendar** - List, create, update, delete events (with Teams meetings)
11
+ - **Tasks** - Manage Microsoft To Do lists and tasks
12
+ - **Planner** - Access plans, buckets, and tasks
13
+ - **OneDrive** - Browse, search, read files
14
+ - **SharePoint** - Access sites and document libraries
15
+ - **Teams** - List teams, channels, read/post messages
16
+ - **Chats** - 1:1 and group chats
17
+ - **Contacts** - List, search, create, update contacts
18
+
19
+ ## Installation
20
+
21
+ ### From npm (recommended)
22
+
23
+ ```bash
24
+ npm install -g awolve-myoffice-cli
25
+ ```
26
+
27
+ ### From source
28
+
29
+ ```bash
30
+ git clone https://github.com/awolve/ops-myoffice.git
31
+ cd ops-myoffice
32
+ npm install
33
+ npm run build
34
+ npm link
35
+ ```
36
+
37
+ ## Prerequisites
38
+
39
+ - Node.js 18+
40
+ - An Azure AD app registration with delegated permissions
41
+ - Microsoft 365 account
42
+
43
+ ## Configuration
44
+
45
+ Add to your shell profile (`~/.zshrc` or `~/.bashrc`):
46
+
47
+ ```bash
48
+ export M365_CLIENT_ID="your-app-client-id"
49
+ # Optional: export M365_TENANT_ID="your-tenant-id"
50
+ ```
51
+
52
+ ## Authentication
53
+
54
+ ```bash
55
+ myoffice login
56
+ ```
57
+
58
+ Opens a browser for device code authentication. Token is cached at `~/.config/myoffice-mcp/msal-cache.json`.
59
+
60
+ ## CLI Usage
61
+
62
+ ```bash
63
+ # Check status
64
+ myoffice status
65
+
66
+ # Email
67
+ myoffice mail list
68
+ myoffice mail list --unread
69
+ myoffice mail read <id>
70
+ myoffice mail send --to user@example.com --subject "Hi" --body "Hello"
71
+
72
+ # Calendar
73
+ myoffice calendar list
74
+ myoffice calendar list --start 2024-01-15 --end 2024-01-20
75
+
76
+ # Tasks (To Do)
77
+ myoffice tasks lists
78
+ myoffice tasks list
79
+ myoffice tasks create "Buy milk"
80
+
81
+ # Files (OneDrive)
82
+ myoffice files list
83
+ myoffice files search "report"
84
+
85
+ # Teams & Chats
86
+ myoffice teams list
87
+ myoffice chats list
88
+
89
+ # Planner
90
+ myoffice planner plans
91
+ myoffice planner tasks <planId>
92
+
93
+ # JSON output (for scripting)
94
+ myoffice mail list --json
95
+ ```
96
+
97
+ Run `myoffice --help` for all commands.
98
+
99
+ ## MCP Server Usage
100
+
101
+ Add to your Claude Code MCP settings:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "myoffice": {
107
+ "command": "myoffice-mcp",
108
+ "env": {
109
+ "M365_CLIENT_ID": "your-client-id"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ Or with full path if not installed globally:
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "myoffice": {
122
+ "command": "node",
123
+ "args": ["/path/to/ops-myoffice/dist/index.js"],
124
+ "env": {
125
+ "M365_CLIENT_ID": "your-client-id"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## Azure AD App Registration
133
+
134
+ 1. Go to [Azure Portal](https://portal.azure.com) > Azure Active Directory > App registrations
135
+ 2. Click **New registration**
136
+ - Name: `MyOffice MCP` (or your choice)
137
+ - Supported account types: **Accounts in any organizational directory**
138
+ - Redirect URI: Leave blank (uses device code flow)
139
+ 3. Note the **Application (client) ID**
140
+ 4. Go to **Authentication** > Enable **Allow public client flows** = Yes
141
+ 5. Go to **API permissions** > Add Microsoft Graph delegated permissions:
142
+ - `Mail.ReadWrite`, `Mail.Send`
143
+ - `Calendars.ReadWrite`
144
+ - `Tasks.ReadWrite`
145
+ - `Files.ReadWrite`, `Sites.Read.All`
146
+ - `Contacts.ReadWrite`
147
+ - `Team.ReadBasic.All`, `Channel.ReadBasic.All`
148
+ - `ChannelMessage.Read.All`, `ChannelMessage.Send`
149
+ - `Chat.Create`, `Chat.ReadBasic`, `Chat.Read`, `ChatMessage.Send`
150
+ - `Tasks.Read`, `Tasks.ReadWrite`, `Group.Read.All` (for Planner)
151
+ - `User.Read`, `offline_access`
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ npm install # Install dependencies
157
+ npm run dev # Run with tsx (no build needed)
158
+ npm run build # Compile TypeScript to dist/
159
+ npm run login # Authenticate with Microsoft
160
+ ```
161
+
162
+ ### Project Structure
163
+
164
+ ```
165
+ src/
166
+ ├── index.ts # MCP server entry point
167
+ ├── cli.ts # CLI entry point (Commander.js)
168
+ ├── core/
169
+ │ └── handler.ts # Shared tool dispatch logic
170
+ ├── cli/
171
+ │ └── formatter.ts # Human-readable output formatting
172
+ ├── auth/ # Authentication (device code flow, token cache)
173
+ ├── tools/ # Microsoft Graph API implementations
174
+ └── utils/ # Graph client, version helper
175
+ ```
176
+
177
+ ### Testing Locally
178
+
179
+ ```bash
180
+ # Test CLI directly (no build needed)
181
+ npx tsx src/cli.ts mail list
182
+
183
+ # Or build first then test
184
+ npm run build
185
+ node dist/cli.js mail list
186
+
187
+ # Test MCP server
188
+ npm run dev
189
+ ```
190
+
191
+ ## Deploying to npm
192
+
193
+ The package is published to npm as `awolve-myoffice-cli`.
194
+
195
+ ### First-Time Setup
196
+
197
+ 1. Create npm account at https://www.npmjs.com/signup
198
+ 2. Login from terminal:
199
+ ```bash
200
+ npm login
201
+ ```
202
+
203
+ ### Publishing a New Version
204
+
205
+ ```bash
206
+ # 1. Make sure you're on main branch with clean working directory
207
+ git checkout main
208
+ git pull
209
+ git status # Should be clean
210
+
211
+ # 2. Run tests / verify everything works
212
+ npm run build
213
+ node dist/cli.js --help
214
+
215
+ # 3. Bump version (choose one)
216
+ npm version patch # 1.1.0 -> 1.1.1 (bug fixes)
217
+ npm version minor # 1.1.0 -> 1.2.0 (new features)
218
+ npm version major # 1.1.0 -> 2.0.0 (breaking changes)
219
+
220
+ # 4. Publish to npm (builds automatically via prepublishOnly)
221
+ npm publish --access public
222
+
223
+ # 5. Push version commit and tag to GitHub
224
+ git push && git push --tags
225
+ ```
226
+
227
+ ### What Gets Published
228
+
229
+ The `files` field in package.json controls what's included:
230
+ - `dist/**/*` - Compiled JavaScript
231
+ - `README.md` - Documentation
232
+
233
+ Source code (`src/`), specs, and dev files are NOT published.
234
+
235
+ ### Verifying Publication
236
+
237
+ ```bash
238
+ # Check package on npm
239
+ npm view awolve-myoffice-cli
240
+
241
+ # Test fresh install
242
+ npm install -g awolve-myoffice-cli
243
+ myoffice --version
244
+ ```
245
+
246
+ ### Important: Version Immutability
247
+
248
+ npm does not allow republishing the same version. Once `1.1.0` is published, that version number is permanently taken. If you need to fix something:
249
+
250
+ 1. Bump to a new version (`npm version patch` → `1.1.1`)
251
+ 2. Publish the new version
252
+
253
+ There is no way to "update" an existing version.
254
+
255
+ ### Troubleshooting
256
+
257
+ - **"You must be logged in"** - Run `npm login`
258
+ - **"Package name already exists"** - Name is taken, choose another
259
+ - **"Cannot publish over existing version"** - Bump version with `npm version patch`
260
+ - **Build fails during publish** - Fix TypeScript errors first
261
+
262
+ ## Security
263
+
264
+ - Uses delegated permissions only - users can only access their own data
265
+ - Tokens stored locally with restrictive permissions
266
+ - No client secrets required (public client)
267
+ - Destructive operations require explicit confirmation
268
+
269
+ ## License
270
+
271
+ MIT
@@ -0,0 +1,20 @@
1
+ import { ICachePlugin, TokenCacheContext } from '@azure/msal-node';
2
+ /**
3
+ * MSAL Cache Plugin that persists the full token cache (including refresh tokens)
4
+ * to a file. This ensures tokens survive server restarts.
5
+ */
6
+ export declare class FileCachePlugin implements ICachePlugin {
7
+ private cachePath;
8
+ constructor(cachePath: string);
9
+ /**
10
+ * Called before MSAL accesses the cache.
11
+ * Load the cache from disk into MSAL's memory.
12
+ */
13
+ beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void>;
14
+ /**
15
+ * Called after MSAL accesses the cache.
16
+ * If the cache changed, persist it to disk.
17
+ */
18
+ afterCacheAccess(cacheContext: TokenCacheContext): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=cache-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-plugin.d.ts","sourceRoot":"","sources":["../../src/auth/cache-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAInE;;;GAGG;AACH,qBAAa,eAAgB,YAAW,YAAY;IAClD,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,MAAM;IAI7B;;;OAGG;IACG,iBAAiB,CAAC,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavE;;;OAGG;IACG,gBAAgB,CAAC,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAcvE"}
@@ -0,0 +1,49 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { dirname } from 'path';
3
+ /**
4
+ * MSAL Cache Plugin that persists the full token cache (including refresh tokens)
5
+ * to a file. This ensures tokens survive server restarts.
6
+ */
7
+ export class FileCachePlugin {
8
+ cachePath;
9
+ constructor(cachePath) {
10
+ this.cachePath = cachePath;
11
+ }
12
+ /**
13
+ * Called before MSAL accesses the cache.
14
+ * Load the cache from disk into MSAL's memory.
15
+ */
16
+ async beforeCacheAccess(cacheContext) {
17
+ try {
18
+ if (existsSync(this.cachePath)) {
19
+ const cacheData = readFileSync(this.cachePath, 'utf-8');
20
+ if (cacheData && cacheData.trim()) {
21
+ cacheContext.tokenCache.deserialize(cacheData);
22
+ }
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.error('[Cache] Failed to load cache:', error instanceof Error ? error.message : error);
27
+ }
28
+ }
29
+ /**
30
+ * Called after MSAL accesses the cache.
31
+ * If the cache changed, persist it to disk.
32
+ */
33
+ async afterCacheAccess(cacheContext) {
34
+ if (cacheContext.cacheHasChanged) {
35
+ try {
36
+ const dir = dirname(this.cachePath);
37
+ if (!existsSync(dir)) {
38
+ mkdirSync(dir, { recursive: true });
39
+ }
40
+ const serialized = cacheContext.tokenCache.serialize();
41
+ writeFileSync(this.cachePath, serialized, { mode: 0o600 });
42
+ }
43
+ catch (error) {
44
+ console.error('[Cache] Failed to save cache:', error instanceof Error ? error.message : error);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=cache-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-plugin.js","sourceRoot":"","sources":["../../src/auth/cache-plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B;;;GAGG;AACH,MAAM,OAAO,eAAe;IAClB,SAAS,CAAS;IAE1B,YAAY,SAAiB;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,iBAAiB,CAAC,YAA+B;QACrD,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/B,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBACxD,IAAI,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;oBAClC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB,CAAC,YAA+B;QACpD,IAAI,YAAY,CAAC,eAAe,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtC,CAAC;gBACD,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;gBACvD,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ export interface TokenCache {
2
+ accessToken: string;
3
+ refreshToken: string;
4
+ expiresAt: number;
5
+ account?: {
6
+ homeAccountId: string;
7
+ environment: string;
8
+ tenantId: string;
9
+ username: string;
10
+ };
11
+ }
12
+ export interface AuthConfig {
13
+ clientId: string;
14
+ tenantId: string;
15
+ scopes: string[];
16
+ }
17
+ export interface StoredConfig {
18
+ clientId?: string;
19
+ tenantId?: string;
20
+ }
21
+ /**
22
+ * Get stored configuration from config file
23
+ */
24
+ export declare function getStoredConfig(): StoredConfig;
25
+ /**
26
+ * Save configuration to config file
27
+ */
28
+ export declare function saveStoredConfig(config: StoredConfig): void;
29
+ /**
30
+ * Get the current auth config (dynamically evaluated)
31
+ * Call this function instead of using DEFAULT_CONFIG directly
32
+ * to ensure env vars and stored config are always checked
33
+ */
34
+ export declare function getAuthConfig(): AuthConfig;
35
+ export declare const DEFAULT_CONFIG: AuthConfig;
36
+ export declare const MSAL_CACHE_FILE: string;
37
+ export declare function getTokenCache(): TokenCache | null;
38
+ export declare function saveTokenCache(cache: TokenCache): void;
39
+ export declare function clearTokenCache(): void;
40
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAU9C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAQ3D;AAuDD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAM1C;AAMD,eAAO,MAAM,cAAc,EAAE,UAI5B,CAAC;AAGF,eAAO,MAAM,eAAe,QAAgE,CAAC;AAE7F,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAUjD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAMtD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAQtC"}
@@ -0,0 +1,135 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join, dirname } from 'path';
4
+ const CONFIG_DIR = join(homedir(), '.config', 'myoffice-mcp');
5
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
6
+ /**
7
+ * Get stored configuration from config file
8
+ */
9
+ export function getStoredConfig() {
10
+ try {
11
+ if (existsSync(CONFIG_FILE)) {
12
+ const data = readFileSync(CONFIG_FILE, 'utf-8');
13
+ return JSON.parse(data);
14
+ }
15
+ }
16
+ catch {
17
+ // Ignore errors, return empty config
18
+ }
19
+ return {};
20
+ }
21
+ /**
22
+ * Save configuration to config file
23
+ */
24
+ export function saveStoredConfig(config) {
25
+ if (!existsSync(CONFIG_DIR)) {
26
+ mkdirSync(CONFIG_DIR, { recursive: true });
27
+ }
28
+ // Merge with existing config
29
+ const existing = getStoredConfig();
30
+ const merged = { ...existing, ...config };
31
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
32
+ }
33
+ /**
34
+ * Get the client ID from env var or stored config
35
+ */
36
+ function getClientId() {
37
+ // Environment variable takes precedence
38
+ if (process.env.M365_CLIENT_ID) {
39
+ return process.env.M365_CLIENT_ID;
40
+ }
41
+ // Fall back to stored config
42
+ const stored = getStoredConfig();
43
+ return stored.clientId || '';
44
+ }
45
+ /**
46
+ * Get the tenant ID from env var or stored config
47
+ */
48
+ function getTenantId() {
49
+ // Environment variable takes precedence
50
+ if (process.env.M365_TENANT_ID) {
51
+ return process.env.M365_TENANT_ID;
52
+ }
53
+ // Fall back to stored config, default to 'common'
54
+ const stored = getStoredConfig();
55
+ return stored.tenantId || 'common';
56
+ }
57
+ // Scopes for Graph API access
58
+ const SCOPES = [
59
+ 'https://graph.microsoft.com/Mail.ReadWrite',
60
+ 'https://graph.microsoft.com/Mail.Send',
61
+ 'https://graph.microsoft.com/Calendars.ReadWrite',
62
+ 'https://graph.microsoft.com/Tasks.ReadWrite',
63
+ 'https://graph.microsoft.com/Files.ReadWrite',
64
+ 'https://graph.microsoft.com/Sites.ReadWrite.All',
65
+ 'https://graph.microsoft.com/Contacts.ReadWrite',
66
+ 'https://graph.microsoft.com/User.Read',
67
+ // Teams
68
+ 'https://graph.microsoft.com/Team.ReadBasic.All',
69
+ 'https://graph.microsoft.com/Channel.ReadBasic.All',
70
+ 'https://graph.microsoft.com/ChannelMessage.Read.All',
71
+ 'https://graph.microsoft.com/ChannelMessage.Send',
72
+ // Chats
73
+ 'https://graph.microsoft.com/Chat.Create',
74
+ 'https://graph.microsoft.com/Chat.ReadBasic',
75
+ 'https://graph.microsoft.com/Chat.Read',
76
+ 'https://graph.microsoft.com/ChatMessage.Send',
77
+ // Planner
78
+ 'https://graph.microsoft.com/Tasks.ReadWrite',
79
+ 'https://graph.microsoft.com/Group.Read.All',
80
+ 'https://graph.microsoft.com/User.ReadBasic.All',
81
+ 'offline_access',
82
+ ];
83
+ /**
84
+ * Get the current auth config (dynamically evaluated)
85
+ * Call this function instead of using DEFAULT_CONFIG directly
86
+ * to ensure env vars and stored config are always checked
87
+ */
88
+ export function getAuthConfig() {
89
+ return {
90
+ clientId: getClientId(),
91
+ tenantId: getTenantId(),
92
+ scopes: SCOPES,
93
+ };
94
+ }
95
+ // Default Azure AD app for personal M365 access
96
+ // Users can override with their own app registration
97
+ // NOTE: This is evaluated once at module load time.
98
+ // For dynamic access, use getAuthConfig() instead.
99
+ export const DEFAULT_CONFIG = {
100
+ clientId: getClientId(),
101
+ tenantId: getTenantId(),
102
+ scopes: SCOPES,
103
+ };
104
+ const TOKEN_FILE = join(homedir(), '.config', 'myoffice-mcp', 'token.json');
105
+ export const MSAL_CACHE_FILE = join(homedir(), '.config', 'myoffice-mcp', 'msal-cache.json');
106
+ export function getTokenCache() {
107
+ try {
108
+ if (existsSync(TOKEN_FILE)) {
109
+ const data = readFileSync(TOKEN_FILE, 'utf-8');
110
+ return JSON.parse(data);
111
+ }
112
+ }
113
+ catch {
114
+ // Ignore errors, return null
115
+ }
116
+ return null;
117
+ }
118
+ export function saveTokenCache(cache) {
119
+ const dir = dirname(TOKEN_FILE);
120
+ if (!existsSync(dir)) {
121
+ mkdirSync(dir, { recursive: true });
122
+ }
123
+ writeFileSync(TOKEN_FILE, JSON.stringify(cache, null, 2), { mode: 0o600 });
124
+ }
125
+ export function clearTokenCache() {
126
+ try {
127
+ if (existsSync(TOKEN_FILE)) {
128
+ writeFileSync(TOKEN_FILE, '', { mode: 0o600 });
129
+ }
130
+ }
131
+ catch {
132
+ // Ignore errors
133
+ }
134
+ }
135
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function authenticateWithDeviceCode(): Promise<void>;
2
+ //# sourceMappingURL=device-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/auth/device-code.ts"],"names":[],"mappings":"AAMA,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CA0ChE"}
@@ -0,0 +1,40 @@
1
+ import { PublicClientApplication } from '@azure/msal-node';
2
+ import { getAuthConfig, MSAL_CACHE_FILE } from './config.js';
3
+ import { FileCachePlugin } from './cache-plugin.js';
4
+ const cachePlugin = new FileCachePlugin(MSAL_CACHE_FILE);
5
+ export async function authenticateWithDeviceCode() {
6
+ const config = getAuthConfig();
7
+ if (!config.clientId) {
8
+ throw new Error('No client ID configured.\n' +
9
+ 'Run: myoffice login --client-id <your-azure-app-client-id>\n' +
10
+ 'Or set M365_CLIENT_ID environment variable.');
11
+ }
12
+ const pca = new PublicClientApplication({
13
+ auth: {
14
+ clientId: config.clientId,
15
+ authority: `https://login.microsoftonline.com/${config.tenantId}`,
16
+ },
17
+ cache: {
18
+ cachePlugin,
19
+ },
20
+ });
21
+ const deviceCodeRequest = {
22
+ scopes: config.scopes,
23
+ deviceCodeCallback: (response) => {
24
+ console.log('\n' + '='.repeat(60));
25
+ console.log('AUTHENTICATION REQUIRED');
26
+ console.log('='.repeat(60));
27
+ console.log(`\n${response.message}\n`);
28
+ console.log('='.repeat(60) + '\n');
29
+ },
30
+ };
31
+ const result = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
32
+ if (!result || !result.accessToken) {
33
+ throw new Error('Failed to acquire access token');
34
+ }
35
+ // The cache plugin automatically persists the tokens via afterCacheAccess
36
+ console.log(`\nAuthenticated as: ${result.account?.username}`);
37
+ console.log('Token cached at:', MSAL_CACHE_FILE);
38
+ console.log('Refresh token is now properly persisted.\n');
39
+ }
40
+ //# sourceMappingURL=device-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/auth/device-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAqB,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,eAAe,CAAC,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,4BAA4B;YAC5B,8DAA8D;YAC9D,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,uBAAuB,CAAC;QACtC,IAAI,EAAE;YACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,qCAAqC,MAAM,CAAC,QAAQ,EAAE;SAClE;QACD,KAAK,EAAE;YACL,WAAW;SACZ;KACF,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAsB;QAC3C,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,wBAAwB,CAAC,iBAAiB,CAAC,CAAC;IAErE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,0EAA0E;IAC1E,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { getAccessToken, isAuthenticated, getCurrentUser } from './token-manager.js';
2
+ export { authenticateWithDeviceCode } from './device-code.js';
3
+ export { DEFAULT_CONFIG, getAuthConfig, getStoredConfig, saveStoredConfig, clearTokenCache } from './config.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { getAccessToken, isAuthenticated, getCurrentUser } from './token-manager.js';
2
+ export { authenticateWithDeviceCode } from './device-code.js';
3
+ export { DEFAULT_CONFIG, getAuthConfig, getStoredConfig, saveStoredConfig, clearTokenCache } from './config.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone login script for initial authentication
4
+ * Run with: npm run login
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":";AACA;;;GAGG"}
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone login script for initial authentication
4
+ * Run with: npm run login
5
+ */
6
+ import { authenticateWithDeviceCode } from './device-code.js';
7
+ async function main() {
8
+ console.log('Personal M365 MCP - Authentication');
9
+ console.log('===================================\n');
10
+ try {
11
+ await authenticateWithDeviceCode();
12
+ console.log('Authentication successful!');
13
+ console.log('You can now use the M365 MCP with Claude Code.');
14
+ process.exit(0);
15
+ }
16
+ catch (error) {
17
+ console.error('Authentication failed:', error);
18
+ process.exit(1);
19
+ }
20
+ }
21
+ main();
22
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAE9D,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IAErD,IAAI,CAAC;QACH,MAAM,0BAA0B,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function getAccessToken(): Promise<string>;
2
+ export declare function isAuthenticated(): Promise<boolean>;
3
+ export declare function getCurrentUser(): Promise<string | null>;
4
+ //# sourceMappingURL=token-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AAsCA,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CA0CtD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAQxD;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ7D"}