@juvantlabs/m365-graph-mcp-server 0.1.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 (110) hide show
  1. package/ARCHITECTURE.md +225 -0
  2. package/CHANGELOG.md +188 -0
  3. package/LICENSE +21 -0
  4. package/README.md +164 -0
  5. package/SECURITY.md +64 -0
  6. package/dist/auth/confirmation_tokens.d.ts +38 -0
  7. package/dist/auth/confirmation_tokens.d.ts.map +1 -0
  8. package/dist/auth/confirmation_tokens.js +85 -0
  9. package/dist/auth/confirmation_tokens.js.map +1 -0
  10. package/dist/auth/keyring.d.ts +20 -0
  11. package/dist/auth/keyring.d.ts.map +1 -0
  12. package/dist/auth/keyring.js +41 -0
  13. package/dist/auth/keyring.js.map +1 -0
  14. package/dist/auth/msal.d.ts +42 -0
  15. package/dist/auth/msal.d.ts.map +1 -0
  16. package/dist/auth/msal.js +96 -0
  17. package/dist/auth/msal.js.map +1 -0
  18. package/dist/auth/setup.d.ts +18 -0
  19. package/dist/auth/setup.d.ts.map +1 -0
  20. package/dist/auth/setup.js +110 -0
  21. package/dist/auth/setup.js.map +1 -0
  22. package/dist/client/graph.d.ts +30 -0
  23. package/dist/client/graph.d.ts.map +1 -0
  24. package/dist/client/graph.js +38 -0
  25. package/dist/client/graph.js.map +1 -0
  26. package/dist/index.d.ts +54 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +131 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/tools/cancel_event.d.ts +18 -0
  31. package/dist/tools/cancel_event.d.ts.map +1 -0
  32. package/dist/tools/cancel_event.js +95 -0
  33. package/dist/tools/cancel_event.js.map +1 -0
  34. package/dist/tools/copy_file.d.ts +39 -0
  35. package/dist/tools/copy_file.d.ts.map +1 -0
  36. package/dist/tools/copy_file.js +168 -0
  37. package/dist/tools/copy_file.js.map +1 -0
  38. package/dist/tools/create_event.d.ts +29 -0
  39. package/dist/tools/create_event.d.ts.map +1 -0
  40. package/dist/tools/create_event.js +144 -0
  41. package/dist/tools/create_event.js.map +1 -0
  42. package/dist/tools/decline_event.d.ts +18 -0
  43. package/dist/tools/decline_event.d.ts.map +1 -0
  44. package/dist/tools/decline_event.js +105 -0
  45. package/dist/tools/decline_event.js.map +1 -0
  46. package/dist/tools/delete_file.d.ts +28 -0
  47. package/dist/tools/delete_file.d.ts.map +1 -0
  48. package/dist/tools/delete_file.js +103 -0
  49. package/dist/tools/delete_file.js.map +1 -0
  50. package/dist/tools/download_file.d.ts +43 -0
  51. package/dist/tools/download_file.d.ts.map +1 -0
  52. package/dist/tools/download_file.js +133 -0
  53. package/dist/tools/download_file.js.map +1 -0
  54. package/dist/tools/get_event.d.ts +27 -0
  55. package/dist/tools/get_event.d.ts.map +1 -0
  56. package/dist/tools/get_event.js +55 -0
  57. package/dist/tools/get_event.js.map +1 -0
  58. package/dist/tools/index.d.ts +13 -0
  59. package/dist/tools/index.d.ts.map +1 -0
  60. package/dist/tools/index.js +61 -0
  61. package/dist/tools/index.js.map +1 -0
  62. package/dist/tools/list_calendars.d.ts +26 -0
  63. package/dist/tools/list_calendars.d.ts.map +1 -0
  64. package/dist/tools/list_calendars.js +60 -0
  65. package/dist/tools/list_calendars.js.map +1 -0
  66. package/dist/tools/list_drives.d.ts +27 -0
  67. package/dist/tools/list_drives.d.ts.map +1 -0
  68. package/dist/tools/list_drives.js +58 -0
  69. package/dist/tools/list_drives.js.map +1 -0
  70. package/dist/tools/list_events.d.ts +51 -0
  71. package/dist/tools/list_events.d.ts.map +1 -0
  72. package/dist/tools/list_events.js +119 -0
  73. package/dist/tools/list_events.js.map +1 -0
  74. package/dist/tools/list_items.d.ts +31 -0
  75. package/dist/tools/list_items.d.ts.map +1 -0
  76. package/dist/tools/list_items.js +81 -0
  77. package/dist/tools/list_items.js.map +1 -0
  78. package/dist/tools/move_file.d.ts +18 -0
  79. package/dist/tools/move_file.d.ts.map +1 -0
  80. package/dist/tools/move_file.js +60 -0
  81. package/dist/tools/move_file.js.map +1 -0
  82. package/dist/tools/search_events.d.ts +25 -0
  83. package/dist/tools/search_events.d.ts.map +1 -0
  84. package/dist/tools/search_events.js +71 -0
  85. package/dist/tools/search_events.js.map +1 -0
  86. package/dist/tools/search_events_content.d.ts +32 -0
  87. package/dist/tools/search_events_content.d.ts.map +1 -0
  88. package/dist/tools/search_events_content.js +106 -0
  89. package/dist/tools/search_events_content.js.map +1 -0
  90. package/dist/tools/search_files.d.ts +30 -0
  91. package/dist/tools/search_files.d.ts.map +1 -0
  92. package/dist/tools/search_files.js +82 -0
  93. package/dist/tools/search_files.js.map +1 -0
  94. package/dist/tools/update_event.d.ts +25 -0
  95. package/dist/tools/update_event.d.ts.map +1 -0
  96. package/dist/tools/update_event.js +123 -0
  97. package/dist/tools/update_event.js.map +1 -0
  98. package/dist/tools/upload_file.d.ts +38 -0
  99. package/dist/tools/upload_file.d.ts.map +1 -0
  100. package/dist/tools/upload_file.js +152 -0
  101. package/dist/tools/upload_file.js.map +1 -0
  102. package/dist/types/tool.d.ts +32 -0
  103. package/dist/types/tool.d.ts.map +1 -0
  104. package/dist/types/tool.js +10 -0
  105. package/dist/types/tool.js.map +1 -0
  106. package/dist/types/validators.d.ts +44 -0
  107. package/dist/types/validators.d.ts.map +1 -0
  108. package/dist/types/validators.js +78 -0
  109. package/dist/types/validators.js.map +1 -0
  110. package/package.json +72 -0
package/SECURITY.md ADDED
@@ -0,0 +1,64 @@
1
+ # Security
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report vulnerabilities **privately** via one of these channels:
6
+
7
+ 1. **GitHub Security Advisory** (preferred) — go to this repo's
8
+ `Security` tab → `Report a vulnerability`. Your report stays
9
+ private between you and the maintainer until we publish a
10
+ coordinated advisory.
11
+ 2. **Email** — `security@juvant.io`. Reports go to the primary
12
+ maintainer.
13
+
14
+ **Please do NOT** open a public issue or pull request that contains
15
+ reproduction details for the vulnerability. Once a public artifact
16
+ exposes the issue, the coordinated-disclosure window collapses.
17
+
18
+ ## What we commit to
19
+
20
+ This repo follows the
21
+ [juvantlabs Security Disclosure Process](https://github.com/juvantlabs/handbook/blob/main/docs/security/disclosure-process.md).
22
+ SLOs:
23
+
24
+ | State | Target |
25
+ |---|---|
26
+ | Acknowledge receipt | ≤ 7 days |
27
+ | Initial triage + severity classification | ≤ 14 days |
28
+ | Patch prepared (high/critical) | ≤ 30 days |
29
+ | Patch prepared (moderate) | ≤ 90 days |
30
+ | Public advisory + CVE | Patch + 1–7 days |
31
+
32
+ ## Supported versions
33
+
34
+ | Version | Supported |
35
+ |---|---|
36
+ | Latest `0.x` (current) | ✅ |
37
+ | Older `0.x` | ❌ End-of-life with each new release until `1.0` |
38
+
39
+ Once `1.0` ships, the supported-versions matrix expands to formally
40
+ back-port security fixes to the `N-1` major.
41
+
42
+ ## Out of scope
43
+
44
+ - Issues in dependencies — please report those upstream. We track
45
+ upstream advisories via Dependabot and bump promptly.
46
+ - Issues in adopter customizations / forks of this server.
47
+ - Theoretical vulnerabilities without a reproduction path.
48
+
49
+ ## Security-relevant dependencies
50
+
51
+ | Dependency | Version | Why it matters |
52
+ |---|---|---|
53
+ | `@modelcontextprotocol/sdk` | `^1.25.2` | ≥ 1.25.2 required to avoid ReDoS (`GHSA-8r9q-7v3j-jr4g`) and DNS rebinding (`GHSA-w48q-cv73-mx4w`) advisories on earlier versions |
54
+ | _(vendor SDK)_ | | _(fill in as the vendor SDK is selected)_ |
55
+
56
+ ## Crediting
57
+
58
+ Reporters are credited by name in advisories unless they request
59
+ anonymity at report time. Past reports + reporter acknowledgments
60
+ will be listed in `SECURITY-CREDITS.md` (created on first disclosure).
61
+
62
+ ## Acknowledgments
63
+
64
+ No disclosures yet.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Server-side state for the spec/approval confirmation-token pattern
3
+ * used by destructive tools (delete_file, cancel_event).
4
+ *
5
+ * Per the handbook MCP server spec § Tool design and ADR 0002:
6
+ * 1. First call: agent submits a spec describing what to delete /
7
+ * cancel; tool returns a preview + a confirmation_token.
8
+ * 2. Agent reviews the preview, returns a second call with the same
9
+ * destructive-op args plus the confirmation_token.
10
+ * 3. Tool verifies (token exists, not expired, not used, tied to
11
+ * THIS tool, args match the original spec) and executes. Token
12
+ * is then consumed (single-use).
13
+ *
14
+ * Token lifetime: 5 minutes. State lives in a module-level Map keyed
15
+ * by token. Cleared on process exit (per-tenant subprocess per
16
+ * handbook spec — no cross-process leakage). Garbage-collected on
17
+ * each issue/consume pass.
18
+ *
19
+ * Spec match is via SHA-256 of canonical JSON (keys sorted) so the
20
+ * agent can't pass a token issued for {item_id: "A"} together with
21
+ * args {item_id: "B"} and have the destructive call go through.
22
+ */
23
+ export interface IssuedToken {
24
+ confirmation_token: string;
25
+ expires_at: string;
26
+ expires_in_seconds: number;
27
+ }
28
+ export declare function issueConfirmation(toolName: string, spec: Record<string, unknown>): IssuedToken;
29
+ export type ConsumeError = "token_unknown" | "token_expired" | "token_wrong_tool" | "spec_mismatch";
30
+ export type ConsumeResult = {
31
+ ok: true;
32
+ } | {
33
+ ok: false;
34
+ error: ConsumeError;
35
+ };
36
+ export declare function consumeConfirmation(token: string, toolName: string, spec: Record<string, unknown>): ConsumeResult;
37
+ export declare function _resetConfirmationTokens(): void;
38
+ //# sourceMappingURL=confirmation_tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmation_tokens.d.ts","sourceRoot":"","sources":["../../src/auth/confirmation_tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAmCH,MAAM,WAAW,WAAW;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,WAAW,CAcb;AAED,MAAM,MAAM,YAAY,GACpB,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,eAAe,CAAC;AAEpB,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;AAEvC,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,aAAa,CAiBf;AAID,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Server-side state for the spec/approval confirmation-token pattern
3
+ * used by destructive tools (delete_file, cancel_event).
4
+ *
5
+ * Per the handbook MCP server spec § Tool design and ADR 0002:
6
+ * 1. First call: agent submits a spec describing what to delete /
7
+ * cancel; tool returns a preview + a confirmation_token.
8
+ * 2. Agent reviews the preview, returns a second call with the same
9
+ * destructive-op args plus the confirmation_token.
10
+ * 3. Tool verifies (token exists, not expired, not used, tied to
11
+ * THIS tool, args match the original spec) and executes. Token
12
+ * is then consumed (single-use).
13
+ *
14
+ * Token lifetime: 5 minutes. State lives in a module-level Map keyed
15
+ * by token. Cleared on process exit (per-tenant subprocess per
16
+ * handbook spec — no cross-process leakage). Garbage-collected on
17
+ * each issue/consume pass.
18
+ *
19
+ * Spec match is via SHA-256 of canonical JSON (keys sorted) so the
20
+ * agent can't pass a token issued for {item_id: "A"} together with
21
+ * args {item_id: "B"} and have the destructive call go through.
22
+ */
23
+ import crypto from "node:crypto";
24
+ const EXPIRY_MS = 5 * 60 * 1000;
25
+ const pending = new Map();
26
+ function canonicalize(spec) {
27
+ // Stable JSON: keys sorted alphabetically. Sufficient for our specs
28
+ // which are flat objects of primitives (strings, numbers, booleans);
29
+ // we don't need full recursive canonicalization.
30
+ const sortedKeys = Object.keys(spec).sort();
31
+ const sorted = {};
32
+ for (const k of sortedKeys)
33
+ sorted[k] = spec[k];
34
+ return JSON.stringify(sorted);
35
+ }
36
+ function hashSpec(spec) {
37
+ return crypto.createHash("sha256").update(canonicalize(spec)).digest("hex");
38
+ }
39
+ function gc() {
40
+ const now = Date.now();
41
+ for (const [k, v] of pending) {
42
+ if (v.expiresAt <= now)
43
+ pending.delete(k);
44
+ }
45
+ }
46
+ export function issueConfirmation(toolName, spec) {
47
+ gc();
48
+ const token = crypto.randomBytes(16).toString("hex");
49
+ const expiresAt = Date.now() + EXPIRY_MS;
50
+ pending.set(token, {
51
+ toolName,
52
+ specHash: hashSpec(spec),
53
+ expiresAt,
54
+ });
55
+ return {
56
+ confirmation_token: token,
57
+ expires_at: new Date(expiresAt).toISOString(),
58
+ expires_in_seconds: Math.floor(EXPIRY_MS / 1000),
59
+ };
60
+ }
61
+ export function consumeConfirmation(token, toolName, spec) {
62
+ gc();
63
+ const entry = pending.get(token);
64
+ if (!entry)
65
+ return { ok: false, error: "token_unknown" };
66
+ if (entry.expiresAt <= Date.now()) {
67
+ pending.delete(token);
68
+ return { ok: false, error: "token_expired" };
69
+ }
70
+ if (entry.toolName !== toolName) {
71
+ return { ok: false, error: "token_wrong_tool" };
72
+ }
73
+ if (entry.specHash !== hashSpec(spec)) {
74
+ return { ok: false, error: "spec_mismatch" };
75
+ }
76
+ // Single-use: consume on success.
77
+ pending.delete(token);
78
+ return { ok: true };
79
+ }
80
+ // Test helper — clears the in-memory token store. Not exported as a
81
+ // tool. Tests import this directly to ensure isolation.
82
+ export function _resetConfirmationTokens() {
83
+ pending.clear();
84
+ }
85
+ //# sourceMappingURL=confirmation_tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmation_tokens.js","sourceRoot":"","sources":["../../src/auth/confirmation_tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Token persistence via the OS keychain.
3
+ *
4
+ * Uses `@napi-rs/keyring` (NOT `keytar` — archived since 2022 per
5
+ * handbook spec anti-pattern #10). Tokens are stored under a
6
+ * (service, account) pair where the account is keyed by tenant ID
7
+ * so multiple tenant configs don't collide.
8
+ *
9
+ * Platform backends:
10
+ * macOS → Keychain
11
+ * Linux → Secret Service / GNOME keyring
12
+ * Windows → Credential Manager
13
+ */
14
+ export interface TokenStore {
15
+ load(): string | null;
16
+ save(serialized: string): void;
17
+ clear(): void;
18
+ }
19
+ export declare function getTokenStore(tenantId: string): TokenStore;
20
+ //# sourceMappingURL=keyring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyring.d.ts","sourceRoot":"","sources":["../../src/auth/keyring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,WAAW,UAAU;IACzB,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAsB1D"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Token persistence via the OS keychain.
3
+ *
4
+ * Uses `@napi-rs/keyring` (NOT `keytar` — archived since 2022 per
5
+ * handbook spec anti-pattern #10). Tokens are stored under a
6
+ * (service, account) pair where the account is keyed by tenant ID
7
+ * so multiple tenant configs don't collide.
8
+ *
9
+ * Platform backends:
10
+ * macOS → Keychain
11
+ * Linux → Secret Service / GNOME keyring
12
+ * Windows → Credential Manager
13
+ */
14
+ import { Entry } from "@napi-rs/keyring";
15
+ const SERVICE = "juvantlabs-m365-graph-mcp-server";
16
+ export function getTokenStore(tenantId) {
17
+ const entry = new Entry(SERVICE, `tenant:${tenantId}`);
18
+ return {
19
+ load() {
20
+ try {
21
+ return entry.getPassword();
22
+ }
23
+ catch {
24
+ // No entry yet — first run before setup, or it was cleared.
25
+ return null;
26
+ }
27
+ },
28
+ save(serialized) {
29
+ entry.setPassword(serialized);
30
+ },
31
+ clear() {
32
+ try {
33
+ entry.deletePassword();
34
+ }
35
+ catch {
36
+ // already absent
37
+ }
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=keyring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyring.js","sourceRoot":"","sources":["../../src/auth/keyring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAQnD,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,UAAU,QAAQ,EAAE,CAAC,CAAC;IACvD,OAAO;QACL,IAAI;YACF,IAAI,CAAC;gBACH,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;gBAC5D,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,IAAI,CAAC,UAAkB;YACrB,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QACD,KAAK;YACH,IAAI,CAAC;gBACH,KAAK,CAAC,cAAc,EAAE,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * MSAL Node client factory + cache plugin wiring.
3
+ *
4
+ * Uses ConfidentialClientApplication (we have a client_secret) with the
5
+ * Authorization Code flow for delegated permissions. Per the handbook
6
+ * spec § Auth, we never roll our own OAuth — MSAL Node is Microsoft's
7
+ * official library and handles refresh, revocation, and edge cases.
8
+ *
9
+ * Tokens persist in the OS keychain via `src/auth/keyring.ts`. The MSAL
10
+ * cache plugin pattern is: load on first access, save when it changes.
11
+ */
12
+ import { ConfidentialClientApplication, type ICachePlugin } from "@azure/msal-node";
13
+ /**
14
+ * Delegated scopes the MCP server requests. Order is irrelevant; MSAL
15
+ * normalizes. `offline_access` is required to get a refresh token.
16
+ *
17
+ * Add scopes here as new tools land — Files.ReadWrite for upload tools,
18
+ * Calendars.ReadWrite for calendar write tools, etc. (per handbook spec
19
+ * § Auth › Scopes: per-tool minimum, justified in ARCHITECTURE.md).
20
+ */
21
+ export declare const DELEGATED_SCOPES: string[];
22
+ /**
23
+ * The redirect URI registered in the Entra app for the OAuth callback.
24
+ * Must exactly match one of the redirect URIs configured in the Entra
25
+ * app registration. See README § Local development.
26
+ */
27
+ export declare const REDIRECT_URI = "http://localhost:3000/auth/callback";
28
+ /**
29
+ * Build the MSAL cache plugin for a given tenant. Exported so tests
30
+ * can drive the load/save lifecycle directly with a fake
31
+ * TokenCacheContext + spied keychain store.
32
+ */
33
+ export declare function makeCachePlugin(tenantId: string): ICachePlugin;
34
+ export declare function makeMsalClient(): ConfidentialClientApplication;
35
+ /**
36
+ * Acquire an access token silently from the cache. Refreshes via the
37
+ * cached refresh token if the access token is expired. Throws if no
38
+ * cached account exists — the caller should run `npm run setup` to
39
+ * complete the initial OAuth flow.
40
+ */
41
+ export declare function getAccessToken(client: ConfidentialClientApplication): Promise<string>;
42
+ //# sourceMappingURL=msal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msal.d.ts","sourceRoot":"","sources":["../../src/auth/msal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,6BAA6B,EAE7B,KAAK,YAAY,EAElB,MAAM,kBAAkB,CAAC;AAI1B;;;;;;;GAOG;AAKH,eAAO,MAAM,gBAAgB,UAK5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAe9D;AAED,wBAAgB,cAAc,IAAI,6BAA6B,CAa9D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * MSAL Node client factory + cache plugin wiring.
3
+ *
4
+ * Uses ConfidentialClientApplication (we have a client_secret) with the
5
+ * Authorization Code flow for delegated permissions. Per the handbook
6
+ * spec § Auth, we never roll our own OAuth — MSAL Node is Microsoft's
7
+ * official library and handles refresh, revocation, and edge cases.
8
+ *
9
+ * Tokens persist in the OS keychain via `src/auth/keyring.ts`. The MSAL
10
+ * cache plugin pattern is: load on first access, save when it changes.
11
+ */
12
+ import { ConfidentialClientApplication, } from "@azure/msal-node";
13
+ import { getTokenStore } from "./keyring.js";
14
+ /**
15
+ * Delegated scopes the MCP server requests. Order is irrelevant; MSAL
16
+ * normalizes. `offline_access` is required to get a refresh token.
17
+ *
18
+ * Add scopes here as new tools land — Files.ReadWrite for upload tools,
19
+ * Calendars.ReadWrite for calendar write tools, etc. (per handbook spec
20
+ * § Auth › Scopes: per-tool minimum, justified in ARCHITECTURE.md).
21
+ */
22
+ // Files.ReadWrite subsumes Files.Read; Calendars.ReadWrite subsumes
23
+ // Calendars.Read. The Entra app permissions list still includes the
24
+ // narrower scopes (granted earlier) — they're harmless, just not
25
+ // requested at token acquisition time.
26
+ export const DELEGATED_SCOPES = [
27
+ "User.Read",
28
+ "Files.ReadWrite",
29
+ "Calendars.ReadWrite",
30
+ "offline_access",
31
+ ];
32
+ /**
33
+ * The redirect URI registered in the Entra app for the OAuth callback.
34
+ * Must exactly match one of the redirect URIs configured in the Entra
35
+ * app registration. See README § Local development.
36
+ */
37
+ export const REDIRECT_URI = "http://localhost:3000/auth/callback";
38
+ /**
39
+ * Build the MSAL cache plugin for a given tenant. Exported so tests
40
+ * can drive the load/save lifecycle directly with a fake
41
+ * TokenCacheContext + spied keychain store.
42
+ */
43
+ export function makeCachePlugin(tenantId) {
44
+ const store = getTokenStore(tenantId);
45
+ return {
46
+ async beforeCacheAccess(cacheContext) {
47
+ const data = store.load();
48
+ if (data) {
49
+ cacheContext.tokenCache.deserialize(data);
50
+ }
51
+ },
52
+ async afterCacheAccess(cacheContext) {
53
+ if (cacheContext.cacheHasChanged) {
54
+ store.save(cacheContext.tokenCache.serialize());
55
+ }
56
+ },
57
+ };
58
+ }
59
+ export function makeMsalClient() {
60
+ const tenantId = process.env.M365_TENANT_ID ?? "";
61
+ const config = {
62
+ auth: {
63
+ clientId: process.env.M365_CLIENT_ID ?? "",
64
+ clientSecret: process.env.M365_CLIENT_SECRET ?? "",
65
+ authority: `https://login.microsoftonline.com/${tenantId}`,
66
+ },
67
+ cache: {
68
+ cachePlugin: makeCachePlugin(tenantId),
69
+ },
70
+ };
71
+ return new ConfidentialClientApplication(config);
72
+ }
73
+ /**
74
+ * Acquire an access token silently from the cache. Refreshes via the
75
+ * cached refresh token if the access token is expired. Throws if no
76
+ * cached account exists — the caller should run `npm run setup` to
77
+ * complete the initial OAuth flow.
78
+ */
79
+ export async function getAccessToken(client) {
80
+ const cache = client.getTokenCache();
81
+ const accounts = await cache.getAllAccounts();
82
+ if (accounts.length === 0) {
83
+ throw new Error("No cached account found in the keychain. Run `npm run setup` (or " +
84
+ "`m365-graph-mcp-server setup`) once to complete the OAuth flow.");
85
+ }
86
+ const result = await client.acquireTokenSilent({
87
+ account: accounts[0],
88
+ scopes: DELEGATED_SCOPES,
89
+ });
90
+ if (!result?.accessToken) {
91
+ throw new Error("Silent token acquisition returned no access token. The refresh " +
92
+ "token may have been revoked or expired. Re-run `npm run setup`.");
93
+ }
94
+ return result.accessToken;
95
+ }
96
+ //# sourceMappingURL=msal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msal.js","sourceRoot":"","sources":["../../src/auth/msal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,6BAA6B,GAI9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;;GAOG;AACH,oEAAoE;AACpE,oEAAoE;AACpE,iEAAiE;AACjE,uCAAuC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,iBAAiB;IACjB,qBAAqB;IACrB,gBAAgB;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAElE;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO;QACL,KAAK,CAAC,iBAAiB,CAAC,YAA+B;YACrD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,IAAI,EAAE,CAAC;gBACT,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QACD,KAAK,CAAC,gBAAgB,CAAC,YAA+B;YACpD,IAAI,YAAY,CAAC,eAAe,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;IAClD,MAAM,MAAM,GAAkB;QAC5B,IAAI,EAAE;YACJ,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE;YAC1C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE;YAClD,SAAS,EAAE,qCAAqC,QAAQ,EAAE;SAC3D;QACD,KAAK,EAAE;YACL,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC;SACvC;KACF,CAAC;IACF,OAAO,IAAI,6BAA6B,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqC;IAErC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,mEAAmE;YACjE,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC;QAC7C,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;QACpB,MAAM,EAAE,gBAAgB;KACzB,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,iEAAiE;YAC/D,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Interactive OAuth setup — run once via `npm run setup` (or
3
+ * `m365-graph-mcp-server setup`) to populate the OS keychain with the
4
+ * initial token cache. After this, `npm run dev` (or `npx ...`) uses
5
+ * the cached refresh token silently for the lifetime of the refresh
6
+ * grant (Microsoft default ~90 days; rolling).
7
+ *
8
+ * Flow:
9
+ * 1. Build the authorization URL via MSAL.
10
+ * 2. Open the user's default browser at that URL.
11
+ * 3. Listen on http://localhost:3000/auth/callback for the
12
+ * ?code=... redirect.
13
+ * 4. Exchange the code for tokens (MSAL writes to the cache plugin
14
+ * → keychain).
15
+ * 5. Close the localhost listener and exit.
16
+ */
17
+ export declare function runSetup(): Promise<void>;
18
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/auth/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkFH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAoC9C"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Interactive OAuth setup — run once via `npm run setup` (or
3
+ * `m365-graph-mcp-server setup`) to populate the OS keychain with the
4
+ * initial token cache. After this, `npm run dev` (or `npx ...`) uses
5
+ * the cached refresh token silently for the lifetime of the refresh
6
+ * grant (Microsoft default ~90 days; rolling).
7
+ *
8
+ * Flow:
9
+ * 1. Build the authorization URL via MSAL.
10
+ * 2. Open the user's default browser at that URL.
11
+ * 3. Listen on http://localhost:3000/auth/callback for the
12
+ * ?code=... redirect.
13
+ * 4. Exchange the code for tokens (MSAL writes to the cache plugin
14
+ * → keychain).
15
+ * 5. Close the localhost listener and exit.
16
+ */
17
+ import { exec } from "node:child_process";
18
+ import http from "node:http";
19
+ import { URL } from "node:url";
20
+ import { DELEGATED_SCOPES, REDIRECT_URI, makeMsalClient } from "./msal.js";
21
+ const CALLBACK_PORT = 3000;
22
+ const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
23
+ function openInBrowser(url) {
24
+ const cmd = process.platform === "darwin"
25
+ ? `open "${url}"`
26
+ : process.platform === "win32"
27
+ ? `start "" "${url}"`
28
+ : `xdg-open "${url}"`;
29
+ exec(cmd, (err) => {
30
+ if (err) {
31
+ console.error("[setup] Could not open browser automatically. Visit the URL above manually.");
32
+ }
33
+ });
34
+ }
35
+ function waitForAuthCode() {
36
+ return new Promise((resolve, reject) => {
37
+ const timer = setTimeout(() => {
38
+ server.close();
39
+ reject(new Error(`Timed out waiting for OAuth callback after ${CALLBACK_TIMEOUT_MS / 1000}s`));
40
+ }, CALLBACK_TIMEOUT_MS);
41
+ const server = http.createServer((req, res) => {
42
+ const requestUrl = new URL(req.url ?? "/", `http://localhost:${CALLBACK_PORT}`);
43
+ if (requestUrl.pathname !== "/auth/callback") {
44
+ res.writeHead(404, { "Content-Type": "text/plain" });
45
+ res.end("Not Found");
46
+ return;
47
+ }
48
+ const code = requestUrl.searchParams.get("code");
49
+ const error = requestUrl.searchParams.get("error");
50
+ const errorDescription = requestUrl.searchParams.get("error_description");
51
+ if (error) {
52
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
53
+ res.end(`<html><body><h2>OAuth error</h2><p><b>${error}</b></p>` +
54
+ `<pre>${errorDescription ?? ""}</pre></body></html>`);
55
+ clearTimeout(timer);
56
+ server.close();
57
+ reject(new Error(`${error}: ${errorDescription ?? "(no description)"}`));
58
+ return;
59
+ }
60
+ if (!code) {
61
+ res.writeHead(400, { "Content-Type": "text/plain" });
62
+ res.end("Missing authorization code in callback URL.");
63
+ return;
64
+ }
65
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
66
+ res.end("<html><body><h2>Authentication successful</h2>" +
67
+ "<p>You can close this tab and return to the terminal.</p></body></html>");
68
+ clearTimeout(timer);
69
+ server.close();
70
+ resolve(code);
71
+ });
72
+ server.on("error", (err) => {
73
+ clearTimeout(timer);
74
+ reject(err);
75
+ });
76
+ server.listen(CALLBACK_PORT, "127.0.0.1");
77
+ });
78
+ }
79
+ export async function runSetup() {
80
+ const client = makeMsalClient();
81
+ const authUrl = await client.getAuthCodeUrl({
82
+ scopes: DELEGATED_SCOPES,
83
+ redirectUri: REDIRECT_URI,
84
+ prompt: "select_account",
85
+ });
86
+ console.error("[setup] Starting OAuth flow against tenant:", process.env.M365_TENANT_ID);
87
+ console.error("[setup] If your browser does not open automatically, visit:");
88
+ console.error("");
89
+ console.error(` ${authUrl}`);
90
+ console.error("");
91
+ console.error(`[setup] Listening for callback on ${REDIRECT_URI}`);
92
+ console.error("");
93
+ openInBrowser(authUrl);
94
+ const code = await waitForAuthCode();
95
+ console.error("[setup] Authorization code received. Exchanging for tokens…");
96
+ const tokenResponse = await client.acquireTokenByCode({
97
+ code,
98
+ scopes: DELEGATED_SCOPES,
99
+ redirectUri: REDIRECT_URI,
100
+ });
101
+ if (!tokenResponse?.account) {
102
+ throw new Error("Token acquisition succeeded but no account info was returned.");
103
+ }
104
+ console.error("[setup] ✓ Tokens cached in OS keychain for:");
105
+ console.error(` username: ${tokenResponse.account.username}`);
106
+ console.error(` homeAccountId: ${tokenResponse.account.homeAccountId}`);
107
+ console.error("");
108
+ console.error("[setup] You can now run `npm run dev` (or `npx @juvantlabs/m365-graph-mcp-server`).");
109
+ }
110
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/auth/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Microsoft Graph client factory.
3
+ *
4
+ * Wraps the official `@microsoft/microsoft-graph-client` with an auth
5
+ * provider that pulls cached tokens from MSAL on every request. MSAL
6
+ * handles refresh transparently; if the refresh fails (revoked
7
+ * token), the auth provider surfaces the error to the caller.
8
+ *
9
+ * Uses isomorphic-fetch (peer dep of the Graph client per its docs).
10
+ * Imported once at module load.
11
+ */
12
+ import "isomorphic-fetch";
13
+ import { Client, type AuthenticationProvider, type AuthenticationProviderOptions } from "@microsoft/microsoft-graph-client";
14
+ import type { ConfidentialClientApplication } from "@azure/msal-node";
15
+ /**
16
+ * Authentication provider that bridges MSAL's token cache → the
17
+ * Microsoft Graph client. Each Graph request triggers
18
+ * `getAccessToken()`, which lets MSAL refresh transparently if the
19
+ * cached token is expired.
20
+ *
21
+ * Exported so tests can verify the bridge without instantiating the
22
+ * full Graph client.
23
+ */
24
+ export declare class MsalAuthProvider implements AuthenticationProvider {
25
+ private readonly msal;
26
+ constructor(msal: ConfidentialClientApplication);
27
+ getAccessToken(_options?: AuthenticationProviderOptions): Promise<string>;
28
+ }
29
+ export declare function makeGraphClient(msal: ConfidentialClientApplication): Client;
30
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/client/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,MAAM,EACN,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EACnC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAItE;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,sBAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,6BAA6B;IAE1D,cAAc,CAAC,QAAQ,CAAC,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC;CAGhF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,6BAA6B,GAAG,MAAM,CAI3E"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Microsoft Graph client factory.
3
+ *
4
+ * Wraps the official `@microsoft/microsoft-graph-client` with an auth
5
+ * provider that pulls cached tokens from MSAL on every request. MSAL
6
+ * handles refresh transparently; if the refresh fails (revoked
7
+ * token), the auth provider surfaces the error to the caller.
8
+ *
9
+ * Uses isomorphic-fetch (peer dep of the Graph client per its docs).
10
+ * Imported once at module load.
11
+ */
12
+ import "isomorphic-fetch";
13
+ import { Client, } from "@microsoft/microsoft-graph-client";
14
+ import { getAccessToken } from "../auth/msal.js";
15
+ /**
16
+ * Authentication provider that bridges MSAL's token cache → the
17
+ * Microsoft Graph client. Each Graph request triggers
18
+ * `getAccessToken()`, which lets MSAL refresh transparently if the
19
+ * cached token is expired.
20
+ *
21
+ * Exported so tests can verify the bridge without instantiating the
22
+ * full Graph client.
23
+ */
24
+ export class MsalAuthProvider {
25
+ msal;
26
+ constructor(msal) {
27
+ this.msal = msal;
28
+ }
29
+ async getAccessToken(_options) {
30
+ return getAccessToken(this.msal);
31
+ }
32
+ }
33
+ export function makeGraphClient(msal) {
34
+ return Client.initWithMiddleware({
35
+ authProvider: new MsalAuthProvider(msal),
36
+ });
37
+ }
38
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/client/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,MAAM,GAGP,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,IAAmC;QAAnC,SAAI,GAAJ,IAAI,CAA+B;IAAG,CAAC;IAEpE,KAAK,CAAC,cAAc,CAAC,QAAwC;QAC3D,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,IAAmC;IACjE,OAAO,MAAM,CAAC,kBAAkB,CAAC;QAC/B,YAAY,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC;KACzC,CAAC,CAAC;AACL,CAAC"}