@juvantlabs/m365-graph-mcp-server 0.3.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -11,6 +11,117 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.4.1] - unreleased — folder creation + confirmation-token expiry semantics
15
+
16
+ > Release-hygiene note: `[0.4.0]` was tag-published on 2026-06-26 from
17
+ > commit `5235c78`. Three subsequent PRs (#9, #11, #12) merged to `main`
18
+ > **without** a version bump on `package.json`, so the source tree
19
+ > drifted from what's on npm. This `0.4.1` restores that alignment. See
20
+ > the root-cause writeup in `docs/adr/` (forthcoming) and the follow-up
21
+ > to add a CI guard that fails a merge when `package.json.version`
22
+ > matches a version already in `npm view … versions`.
23
+
24
+ ### Added — Files
25
+
26
+ - **`m365-graph:create_folder`** — new tool for creating folders in
27
+ OneDrive personal drives and SharePoint document libraries. Wraps
28
+ `POST /drives/{drive-id}/items/{parent-id}/children` with the
29
+ `folder: {}` facet. Parameters: `name` (required), `parent_item_id?`
30
+ (defaults to drive root), `drive_id?` (defaults to `/me/drive`),
31
+ `conflict_behavior?` — **restricted to `fail` (default) | `rename`
32
+ by design**. `replace` is intentionally NOT exposed: Graph accepts
33
+ it, but replacing an existing folder is a near-destructive intent
34
+ (the existing folder — potentially with content — is displaced)
35
+ that would push this tool from `write_idempotent` toward
36
+ `write_irreversible` semantics and force a two-phase confirmation
37
+ gate. Callers that truly need replacement compose it from
38
+ `delete_file` (already two-phase-gated) + `create_folder`.
39
+ Returns the created folder's item ID so agents can chain
40
+ `upload_file(parent_item_id=<new_id>, …)` without a round-trip
41
+ through `list_items`. Categorized `write_idempotent` — repeated
42
+ calls with the default `fail` behavior converge on a single
43
+ effective state, and the created folder is reversible via
44
+ `delete_file`. No new Graph scope required: `Files.ReadWrite`
45
+ (OneDrive) and `Sites.ReadWrite.All` (SharePoint) already granted
46
+ in 0.4.0. Filed as issue #10; motivated by real friction archiving
47
+ invoices into the `finance-invoices` SharePoint library where
48
+ destination folders (`Aruba/`, `_SDI-ufficiali/`, `Namecheap/`,
49
+ `_Personal-pre_startup/`) had to be created by hand in the web UI
50
+ before the server could upload into them. Total tool count is now
51
+ 20 (5 write on files: upload, create_folder, copy, move, delete).
52
+
53
+ ### Fixed
54
+
55
+ - **`consumeConfirmation` — expired token now reports `token_expired`,
56
+ not `token_unknown`** (`src/auth/confirmation_tokens.ts`). Pre-fix, the
57
+ periodic `gc()` pass ran BEFORE the entry lookup, so an expired-but-
58
+ still-recorded entry was garbage-collected first and the subsequent
59
+ `pending.get(token)` returned `undefined` — the caller saw
60
+ `token_unknown` and the `token_expired` code path was effectively
61
+ unreachable from wall-clock expiry. The two states are semantically
62
+ distinct (expired = "you're late"; unknown = "there's no such token")
63
+ and both are audit-relevant on a security-relevant two-phase gate
64
+ (`delete_file`, `cancel_event`, `decline_event`). Fix: look up the
65
+ entry FIRST, distinguish expired vs unknown, then run `gc()` on the
66
+ rest of the store. A new `_injectExpiredConfirmation` test helper and
67
+ two unit tests assert the distinct outcomes without racing wall-clock
68
+ time. Surfaced by a sibling MCP server (`m365-mail`) while porting the
69
+ same confirmation-token module. No behavior change on any other
70
+ outcome (`ok`, `token_wrong_tool`, `spec_mismatch` all unchanged).
71
+ This fix was originally listed under `[0.4.0]` in the pre-audit
72
+ changelog, but did NOT make it into the npm-published 0.4.0 tarball
73
+ (verified by tarball inspection on 2026-07-04); it lands in the user-
74
+ visible surface only with this 0.4.1.
75
+
76
+ ---
77
+
78
+ ## [0.4.0] - 2026-06-26 — SharePoint sites RW + permission-surface invariants
79
+
80
+ ### Added
81
+
82
+ - **Delegated scope `Sites.ReadWrite.All`** in `src/auth/msal.ts`. Enables
83
+ read/write against SharePoint document libraries via the existing file
84
+ tools. Deliberately scoped: `Sites.Manage.All`, `Sites.FullControl.All`,
85
+ and `Application.ReadWrite.All` are NOT requested — those carry
86
+ permission-mutation privileges (sharing-link creation, guest invites,
87
+ permission grants, ownership transfer) outside the v0.4.0 threat model.
88
+ - **`ToolCategory` value `permission_mutating`** (`src/types/tool.ts`),
89
+ distinct from `write_irreversible`. Reserved for operations that create
90
+ or modify standing access / sharing / ownership. v0.4.0 ships with zero
91
+ tools in this category — it is a pre-classification slot.
92
+
93
+ ### Security
94
+
95
+ - **CI Layer A — permission-mutation surface invariant**
96
+ (`.github/workflows/ci.yml`). Two checks fail the build:
97
+ (1) the MSAL scope set in `src/auth/msal.ts` must not include
98
+ `Sites.Manage.All` / `Sites.FullControl.All` / `Application.ReadWrite.All`
99
+ unless at least one tool is classified `permission_mutating` AND its
100
+ filename is on `PERMISSION_MUTATING_ALLOWLIST` inside the workflow;
101
+ (2) any tool source under `src/tools/` matching a permission/sharing
102
+ Graph endpoint shape (`/permissions`, `createLink`, `/invite`,
103
+ `assignSensitivityLabel`, `transferOwnership`) must be classified
104
+ `permission_mutating` and on the same allowlist. The allowlist is
105
+ intentionally maintained inline in the workflow so additions surface
106
+ in code review.
107
+ - **CI Layer B — synthetic permission-surface canary**
108
+ (`tests/unit/permission_surface.test.ts`). Static assertion over
109
+ `ALL_TOOLS` that every exposed tool is either scopable (declares a
110
+ drive/site/item/event/meeting/calendar/transcript id parameter in its
111
+ inputSchema), on the `NO_TARGET_TOOLS` allowlist, or classified
112
+ `permission_mutating`. Runs unconditionally — no tenant calls, no
113
+ secrets — so PR builds without `VENDOR_SANDBOX_TOKEN` still gate.
114
+ - Baseline assertion: zero `permission_mutating` tools in v0.4.0.
115
+ Introducing the first one requires updating BOTH the test floor AND
116
+ the workflow allowlist in the same PR.
117
+
118
+ Reference: decisions#210 (per-agent legal-ip enforcement architecture).
119
+
120
+ Published from tag `v0.4.0` → commit `5235c78`. **Note**: the fix
121
+ `consumeConfirmation reports token_expired distinctly from
122
+ token_unknown` was originally targeted for this release but landed on
123
+ `main` after the tag was cut; it ships in `0.4.1`.
124
+
14
125
  ---
15
126
 
16
127
  ## [0.3.0] - 2026-06-22 — Long Teams transcript paging + cap fix
package/README.md CHANGED
@@ -32,10 +32,13 @@ See [CHANGELOG.md](CHANGELOG.md) for the full change list.
32
32
 
33
33
  ## Status
34
34
 
35
- **Published.** v0.3.0 on npm
36
- ([`@juvantlabs/m365-graph-mcp-server`](https://www.npmjs.com/package/@juvantlabs/m365-graph-mcp-server)).
37
- 19 tools across files (OneDrive + SharePoint), Outlook Calendar, and
38
- Teams meeting transcripts. Published via npm **Trusted Publishing**
35
+ **Published.** v0.4.0 on npm
36
+ ([`@juvantlabs/m365-graph-mcp-server`](https://www.npmjs.com/package/@juvantlabs/m365-graph-mcp-server)),
37
+ released 2026-06-26. 19 tools across files (OneDrive + SharePoint),
38
+ Outlook Calendar, and Teams meeting transcripts. v0.4.1 is prepared
39
+ on `main` — adds `create_folder` (bringing the tool count to 20) and
40
+ fixes the `consumeConfirmation` gc-ordering bug; pending manual tag +
41
+ release trigger. Published via npm **Trusted Publishing**
39
42
  (OIDC-based auth from GitHub Actions; no static `NPM_TOKEN`) with
40
43
  provenance attestation; manual approval gate on the `production`
41
44
  GitHub Environment guards the publish step.
@@ -96,7 +99,7 @@ The Juvant OS adopter binds this server in `.juvant/config.json`:
96
99
  {
97
100
  "m365-graph": {
98
101
  "provider": "microsoft",
99
- "mcp_server": "npx @juvantlabs/m365-graph-mcp-server@0.3.0",
102
+ "mcp_server": "npx @juvantlabs/m365-graph-mcp-server@0.4.0",
100
103
  "scope": "rw"
101
104
  }
102
105
  }
@@ -122,6 +125,7 @@ Step 8.5 cross-check semantics.
122
125
  | `m365-graph:search_events` | Searches events by subject substring (Graph $search isn't supported on Events; subject-only via `contains()`). Returns recurrence series masters, not occurrences. | `query` (required), `limit?` (1–50, default 20) | `{ count, results: [] }` (same event shape). | `Calendars.Read` |
123
126
  | `m365-graph:get_event` | Fetches full details for a single event — body (capped at 8000 chars), attendees with response statuses, location, recurrence rule, online-meeting join URL when applicable. | `event_id` (required) | event summary + `body` / `body_content_type` / `body_truncated` / `recurrence` / `online_meeting_join_url`. | `Calendars.Read` |
124
127
  | `m365-graph:upload_file` | Uploads a local file to a drive. Auto-routes between single PUT (≤ 4 MB) and resumable upload session (> 4 MB, 10 MB chunks). 200 MB hard cap. | `local_path` (required), `drive_id?`, `parent_item_id?`, `name?`, `conflict_behavior?` (`fail`/`replace`/`rename`, default `fail`) | `{ uploaded: { id, name, size, webUrl, upload_path } }` | `Files.ReadWrite` |
128
+ | `m365-graph:create_folder` | Creates a folder in a drive (OneDrive primary by default; SharePoint document library via `drive_id`). Synchronous POST to `/children`. Returns the new folder's item ID so it can be passed as `parent_item_id` to a subsequent `upload_file` / `create_folder` call. `replace` conflict behavior is intentionally not exposed (compose `delete_file` + `create_folder` if truly needed). | `name` (required), `parent_item_id?`, `drive_id?`, `conflict_behavior?` (`fail`/`rename`, default `fail`) | `{ created: { id, name, webUrl, parent_id, child_count } }` | `Files.ReadWrite` |
125
129
  | `m365-graph:create_event` | Creates a new event on the user's primary calendar (or a specified calendar). Sends invitations to attendees by Graph default. | `subject` + `start` + `end` (required), `timezone?` (default UTC), `body?`, `body_content_type?` (`text`/`html`), `location?`, `attendees?`, `is_all_day?`, `calendar_id?` | `{ created: <event summary> }` | `Calendars.ReadWrite` |
126
130
  | `m365-graph:update_event` | Updates an existing event. All fields except `event_id` are optional; only provided fields are PATCHed. **Attendees: full replacement, not merge** — pass the full intended list. | `event_id` (required), then any subset of `subject`/`start`+`end`+`timezone`/`body`+`body_content_type`/`location`/`attendees`/`is_all_day` | `{ updated: <event summary> }` | `Calendars.ReadWrite` |
127
131
  | `m365-graph:copy_file` | Async copy with polling. POSTs to `/items/{id}/copy`, polls the monitor URL with exponential backoff (1s → 2s → … capped at 30s) until completion. Falls back to `list-by-name` if the monitor's completed response omits `resourceLocation` (common Graph quirk). | `item_id` + `target_parent_id` (required); `source_drive_id?`, `target_drive_id?`, `new_name?`, `wait_max_seconds?` (1–1800, default 300) | `{ status: "completed", copied: { id, name, ... } }` | `Files.ReadWrite` |
@@ -135,7 +139,7 @@ Step 8.5 cross-check semantics.
135
139
 
136
140
  ¹ **Admin consent required.** `OnlineMeetings.Read` and `OnlineMeetingTranscript.Read.All` must be granted in the Entra app registration under **API permissions → Add a permission → Microsoft Graph → Delegated → Grant admin consent**. Without admin consent these tools return 403 Forbidden.
137
141
 
138
- That's 4 read + 4 write on files, 5 read + 4 write on calendars, 2 read on meeting transcripts — **19 tools total**. Read tools exercise delegated `Files.Read` + `Calendars.Read`; write tools require `Files.ReadWrite` + `Calendars.ReadWrite`; transcript tools require `OnlineMeetings.Read` + `OnlineMeetingTranscript.Read.All` (all separately granted + admin-consented in the Entra app).
142
+ That's 4 read + 5 write on files, 5 read + 4 write on calendars, 2 read on meeting transcripts — **20 tools total**. Read tools exercise delegated `Files.Read` + `Calendars.Read`; write tools require `Files.ReadWrite` + `Calendars.ReadWrite`; transcript tools require `OnlineMeetings.Read` + `OnlineMeetingTranscript.Read.All` (all separately granted + admin-consented in the Entra app).
139
143
 
140
144
  ## Local development
141
145
 
@@ -35,4 +35,5 @@ export type ConsumeResult = {
35
35
  };
36
36
  export declare function consumeConfirmation(token: string, toolName: string, spec: Record<string, unknown>): ConsumeResult;
37
37
  export declare function _resetConfirmationTokens(): void;
38
+ export declare function _injectExpiredConfirmation(token: string, toolName: string, spec: Record<string, unknown>): void;
38
39
  //# sourceMappingURL=confirmation_tokens.d.ts.map
@@ -1 +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"}
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,CA4Bf;AAID,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAID,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,IAAI,CAMN"}
@@ -59,14 +59,24 @@ export function issueConfirmation(toolName, spec) {
59
59
  };
60
60
  }
61
61
  export function consumeConfirmation(token, toolName, spec) {
62
- gc();
62
+ // Look up the token BEFORE the periodic GC pass so an expired-but-
63
+ // still-recorded entry reports as `token_expired`, not `token_unknown`
64
+ // — the two states are semantically distinct (expired says "you're
65
+ // late"; unknown says "there's no such token"). Only after this
66
+ // lookup do we GC other stale entries. For a security-relevant two-
67
+ // phase gate (delete_file, cancel_event, decline_event) this matters
68
+ // for the caller-facing UX and for audit signal fidelity.
63
69
  const entry = pending.get(token);
64
- if (!entry)
70
+ if (!entry) {
71
+ gc();
65
72
  return { ok: false, error: "token_unknown" };
73
+ }
66
74
  if (entry.expiresAt <= Date.now()) {
67
75
  pending.delete(token);
76
+ gc();
68
77
  return { ok: false, error: "token_expired" };
69
78
  }
79
+ gc();
70
80
  if (entry.toolName !== toolName) {
71
81
  return { ok: false, error: "token_wrong_tool" };
72
82
  }
@@ -82,4 +92,13 @@ export function consumeConfirmation(token, toolName, spec) {
82
92
  export function _resetConfirmationTokens() {
83
93
  pending.clear();
84
94
  }
95
+ // Test helper — inject a pre-expired token so expiry paths can be
96
+ // exercised deterministically without racing wall-clock time.
97
+ export function _injectExpiredConfirmation(token, toolName, spec) {
98
+ pending.set(token, {
99
+ toolName,
100
+ specHash: hashSpec(spec),
101
+ expiresAt: Date.now() - 1_000,
102
+ });
103
+ }
85
104
  //# sourceMappingURL=confirmation_tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"confirmation_tokens.js","sourceRoot":"","sources":["../../src/auth/confirmation_tokens.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"confirmation_tokens.js","sourceRoot":"","sources":["../../src/auth/confirmation_tokens.ts"],"names":[],"mappings":"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"}
@@ -1 +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,UAO5B,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"}
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;AAcH,eAAO,MAAM,gBAAgB,UAQ5B,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"}
package/dist/auth/msal.js CHANGED
@@ -23,9 +23,19 @@ import { getTokenStore } from "./keyring.js";
23
23
  // Calendars.Read. The Entra app permissions list still includes the
24
24
  // narrower scopes (granted earlier) — they're harmless, just not
25
25
  // requested at token acquisition time.
26
+ //
27
+ // `Sites.ReadWrite.All` (added v0.4.0): delegated scope for SharePoint
28
+ // document-library RW. Deliberately *not* `Sites.Manage.All` /
29
+ // `Sites.FullControl.All` / `Application.ReadWrite.All` — those carry
30
+ // permission-mutation privileges (sharing-link creation, guest invites,
31
+ // permission grants, ownership transfer) that exceed the v0.4.0 threat
32
+ // model. CI Layer A in `.github/workflows/ci.yml` fails the build if
33
+ // any of those broader scopes are introduced without a matching
34
+ // `permission_mutating` tool classification + allowlist entry.
26
35
  export const DELEGATED_SCOPES = [
27
36
  "User.Read",
28
37
  "Files.ReadWrite",
38
+ "Sites.ReadWrite.All",
29
39
  "Calendars.ReadWrite",
30
40
  "OnlineMeetings.Read",
31
41
  "OnlineMeetingTranscript.Read.All",
@@ -1 +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,qBAAqB;IACrB,kCAAkC;IAClC,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"}
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,EAAE;AACF,uEAAuE;AACvE,+DAA+D;AAC/D,sEAAsE;AACtE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,gEAAgE;AAChE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,iBAAiB;IACjB,qBAAqB;IACrB,qBAAqB;IACrB,qBAAqB;IACrB,kCAAkC;IAClC,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,64 @@
1
+ /**
2
+ * Tool: m365-graph:create_folder
3
+ *
4
+ * Create a folder in a drive (OneDrive primary by default; SharePoint
5
+ * document library via `drive_id`). Wraps Graph's synchronous
6
+ * children-POST:
7
+ *
8
+ * POST /me/drive/root/children (root of primary OneDrive)
9
+ * POST /me/drive/items/{parent-id}/children (nested)
10
+ * POST /drives/{drive-id}/root/children (non-default drive)
11
+ * POST /drives/{drive-id}/items/{parent-id}/children (nested)
12
+ *
13
+ * Body:
14
+ * {
15
+ * "name": "<name>",
16
+ * "folder": {},
17
+ * "@microsoft.graph.conflictBehavior": "fail" | "replace" | "rename"
18
+ * }
19
+ *
20
+ * Conflict behavior — deliberately restricted to two values:
21
+ * - `fail` (default) — refuse if an item with that name exists
22
+ * - `rename` — let Graph auto-suffix (`Foo` → `Foo 1`)
23
+ *
24
+ * `replace` is NOT exposed. Graph would accept it, but replacing an
25
+ * existing folder is a rare, near-destructive intent (the existing
26
+ * folder — potentially with content — is displaced) that would push
27
+ * this tool from `write_idempotent` toward `write_irreversible`
28
+ * semantics and force a two-phase confirmation gate. Excluded by
29
+ * design; if a caller truly needs replacement they compose it from
30
+ * `delete_file` (already two-phase-gated) + `create_folder`.
31
+ *
32
+ * Category: `write_idempotent` — a repeated call with
33
+ * `conflict_behavior: fail` returns an error rather than creating a
34
+ * duplicate; the created folder is reversible via `delete_file`. No
35
+ * two-phase confirmation needed (creation, not destruction).
36
+ *
37
+ * Required Graph scope: `Files.ReadWrite` (delegated) for OneDrive;
38
+ * `Sites.ReadWrite.All` (delegated) for SharePoint document libraries.
39
+ * Both are already requested by src/auth/msal.ts as of v0.4.0.
40
+ *
41
+ * Path/name notes:
42
+ * - We only address the parent by ID (`parent_item_id`), consistent
43
+ * with the rest of the file-tool surface. Path-string addressing
44
+ * is intentionally out of scope.
45
+ * - Names with leading underscore (`_SDI-ufficiali`) or spaces
46
+ * (`New Folder`) are legal in OneDrive/SharePoint doc libraries;
47
+ * we pass through unmodified. Reserved characters (`\ / : * ? " <
48
+ * > |`) and reserved DOS-style names (CON, PRN, etc.) are rejected
49
+ * by Graph with a 400 that surfaces back to the agent as a clear
50
+ * error — no client-side sanitization that would silently mutate
51
+ * the requested name.
52
+ */
53
+ import type { Tool } from "../types/tool.js";
54
+ interface CreatedFolderSummary {
55
+ id: string;
56
+ name: string;
57
+ webUrl: string;
58
+ parent_id: string | null;
59
+ child_count: number;
60
+ }
61
+ export declare function summarizeCreatedFolder(item: Record<string, unknown>): CreatedFolderSummary;
62
+ export declare const createFolderTool: Tool;
63
+ export {};
64
+ //# sourceMappingURL=create_folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create_folder.d.ts","sourceRoot":"","sources":["../../src/tools/create_folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AASH,OAAO,KAAK,EAAE,IAAI,EAA6C,MAAM,kBAAkB,CAAC;AAsCxF,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAU1F;AAmDD,eAAO,MAAM,gBAAgB,EAAE,IAA4D,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Tool: m365-graph:create_folder
3
+ *
4
+ * Create a folder in a drive (OneDrive primary by default; SharePoint
5
+ * document library via `drive_id`). Wraps Graph's synchronous
6
+ * children-POST:
7
+ *
8
+ * POST /me/drive/root/children (root of primary OneDrive)
9
+ * POST /me/drive/items/{parent-id}/children (nested)
10
+ * POST /drives/{drive-id}/root/children (non-default drive)
11
+ * POST /drives/{drive-id}/items/{parent-id}/children (nested)
12
+ *
13
+ * Body:
14
+ * {
15
+ * "name": "<name>",
16
+ * "folder": {},
17
+ * "@microsoft.graph.conflictBehavior": "fail" | "replace" | "rename"
18
+ * }
19
+ *
20
+ * Conflict behavior — deliberately restricted to two values:
21
+ * - `fail` (default) — refuse if an item with that name exists
22
+ * - `rename` — let Graph auto-suffix (`Foo` → `Foo 1`)
23
+ *
24
+ * `replace` is NOT exposed. Graph would accept it, but replacing an
25
+ * existing folder is a rare, near-destructive intent (the existing
26
+ * folder — potentially with content — is displaced) that would push
27
+ * this tool from `write_idempotent` toward `write_irreversible`
28
+ * semantics and force a two-phase confirmation gate. Excluded by
29
+ * design; if a caller truly needs replacement they compose it from
30
+ * `delete_file` (already two-phase-gated) + `create_folder`.
31
+ *
32
+ * Category: `write_idempotent` — a repeated call with
33
+ * `conflict_behavior: fail` returns an error rather than creating a
34
+ * duplicate; the created folder is reversible via `delete_file`. No
35
+ * two-phase confirmation needed (creation, not destruction).
36
+ *
37
+ * Required Graph scope: `Files.ReadWrite` (delegated) for OneDrive;
38
+ * `Sites.ReadWrite.All` (delegated) for SharePoint document libraries.
39
+ * Both are already requested by src/auth/msal.ts as of v0.4.0.
40
+ *
41
+ * Path/name notes:
42
+ * - We only address the parent by ID (`parent_item_id`), consistent
43
+ * with the rest of the file-tool surface. Path-string addressing
44
+ * is intentionally out of scope.
45
+ * - Names with leading underscore (`_SDI-ufficiali`) or spaces
46
+ * (`New Folder`) are legal in OneDrive/SharePoint doc libraries;
47
+ * we pass through unmodified. Reserved characters (`\ / : * ? " <
48
+ * > |`) and reserved DOS-style names (CON, PRN, etc.) are rejected
49
+ * by Graph with a 400 that surfaces back to the agent as a clear
50
+ * error — no client-side sanitization that would silently mutate
51
+ * the requested name.
52
+ */
53
+ import { validateOptionalEnum, validateOptionalString, validateRequiredString, } from "../types/validators.js";
54
+ const CONFLICT_BEHAVIORS = ["fail", "rename"];
55
+ const definition = {
56
+ name: "m365-graph:create_folder",
57
+ description: "Create a folder in a drive (OneDrive primary by default; SharePoint document library via drive_id). Synchronous POST to /children. Default conflict behavior is 'fail' — set to 'rename' to auto-suffix on collision. 'replace' is intentionally not supported (replacing an existing folder is near-destructive; compose delete_file + create_folder if you truly need replacement). Returns the created folder's item ID so it can be passed as parent_item_id to a subsequent upload_file / create_folder call.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ name: {
62
+ type: "string",
63
+ description: "Folder name to create. Passed through unmodified — Graph rejects reserved characters (\\ / : * ? \" < > |) with a 400 the agent can act on.",
64
+ },
65
+ parent_item_id: {
66
+ type: "string",
67
+ description: "Optional parent folder ID to create INTO. Defaults to the drive root. Get from list_items.",
68
+ },
69
+ drive_id: {
70
+ type: "string",
71
+ description: "Optional drive ID. Defaults to the user's primary OneDrive. Use for SharePoint document libraries.",
72
+ },
73
+ conflict_behavior: {
74
+ type: "string",
75
+ enum: ["fail", "rename"],
76
+ description: "What to do if an item with this name already exists in the parent. 'fail' (default) refuses; 'rename' lets Graph auto-suffix (Foo → Foo 1). 'replace' is intentionally not exposed — see tool description.",
77
+ },
78
+ },
79
+ required: ["name"],
80
+ },
81
+ };
82
+ export function summarizeCreatedFolder(item) {
83
+ const parent = item.parentReference;
84
+ const folder = item.folder;
85
+ return {
86
+ id: String(item.id ?? ""),
87
+ name: String(item.name ?? ""),
88
+ webUrl: String(item.webUrl ?? ""),
89
+ parent_id: parent?.id ?? null,
90
+ child_count: Number(folder?.childCount ?? 0),
91
+ };
92
+ }
93
+ const handler = async (graph, args) => {
94
+ const name = validateRequiredString(args.name, "name");
95
+ const parentItemId = validateOptionalString(args.parent_item_id, "parent_item_id");
96
+ const driveId = validateOptionalString(args.drive_id, "drive_id");
97
+ const conflictBehavior = validateOptionalEnum(args.conflict_behavior, "conflict_behavior", CONFLICT_BEHAVIORS, "fail");
98
+ const drivePath = driveId ? `/drives/${encodeURIComponent(driveId)}` : "/me/drive";
99
+ const parentPath = parentItemId
100
+ ? `${drivePath}/items/${encodeURIComponent(parentItemId)}/children`
101
+ : `${drivePath}/root/children`;
102
+ const body = {
103
+ name,
104
+ folder: {},
105
+ "@microsoft.graph.conflictBehavior": conflictBehavior,
106
+ };
107
+ console.error(`[m365-graph-mcp-server] create_folder: ${drivePath}/${parentItemId ?? "<root>"}/${name} (conflict=${conflictBehavior})`);
108
+ const created = (await graph.api(parentPath).post(body));
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify({
114
+ drive_id: driveId ?? null,
115
+ parent_item_id: parentItemId ?? null,
116
+ created: summarizeCreatedFolder(created),
117
+ }, null, 2),
118
+ },
119
+ ],
120
+ };
121
+ };
122
+ export const createFolderTool = { category: "write_idempotent", definition, handler };
123
+ //# sourceMappingURL=create_folder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create_folder.js","sourceRoot":"","sources":["../../src/tools/create_folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAIH,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAU,CAAC;AAGvD,MAAM,UAAU,GAAmB;IACjC,IAAI,EAAE,0BAA0B;IAChC,WAAW,EACT,ofAAof;IACtf,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,6IAA6I;aAChJ;YACD,cAAc,EAAE;gBACd,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,4FAA4F;aAC/F;YACD,QAAQ,EAAE;gBACR,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,oGAAoG;aACvG;YACD,iBAAiB,EAAE;gBACjB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;gBACxB,WAAW,EACT,4MAA4M;aAC/M;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB;CACF,CAAC;AAUF,MAAM,UAAU,sBAAsB,CAAC,IAA6B;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,eAAsD,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,MAA6C,CAAC;IAClE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QACjC,SAAS,EAAG,MAAM,EAAE,EAAyB,IAAI,IAAI;QACrD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAgB,KAAK,EAChC,KAAa,EACb,IAA6B,EACN,EAAE;IACzB,MAAM,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAClE,MAAM,gBAAgB,GAAG,oBAAoB,CAC3C,IAAI,CAAC,iBAAiB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,CACP,CAAC;IAEF,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACnF,MAAM,UAAU,GAAG,YAAY;QAC7B,CAAC,CAAC,GAAG,SAAS,UAAU,kBAAkB,CAAC,YAAY,CAAC,WAAW;QACnE,CAAC,CAAC,GAAG,SAAS,gBAAgB,CAAC;IAEjC,MAAM,IAAI,GAA4B;QACpC,IAAI;QACJ,MAAM,EAAE,EAAE;QACV,mCAAmC,EAAE,gBAAgB;KACtD,CAAC;IAEF,OAAO,CAAC,KAAK,CACX,0CAA0C,SAAS,IAAI,YAAY,IAAI,QAAQ,IAAI,IAAI,cAAc,gBAAgB,GAAG,CACzH,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAA4B,CAAC;IAEpF,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;oBACE,QAAQ,EAAE,OAAO,IAAI,IAAI;oBACzB,cAAc,EAAE,YAAY,IAAI,IAAI;oBACpC,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC;iBACzC,EACD,IAAI,EACJ,CAAC,CACF;aACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAS,EAAE,QAAQ,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AA0B7C,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,IAAI,CAyBzC,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAMxF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AA2B7C,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,IAAI,CA0BzC,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAMxF"}
@@ -14,6 +14,7 @@ import { listItemsTool } from "./list_items.js";
14
14
  import { searchFilesTool } from "./search_files.js";
15
15
  // Files — write
16
16
  import { copyFileTool } from "./copy_file.js";
17
+ import { createFolderTool } from "./create_folder.js";
17
18
  import { deleteFileTool } from "./delete_file.js";
18
19
  import { moveFileTool } from "./move_file.js";
19
20
  import { uploadFileTool } from "./upload_file.js";
@@ -39,6 +40,7 @@ export const ALL_TOOLS = [
39
40
  downloadFileTool,
40
41
  // Files — write
41
42
  uploadFileTool,
43
+ createFolderTool,
42
44
  copyFileTool,
43
45
  moveFileTool,
44
46
  deleteFileTool,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,eAAe;AACf,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,gCAAgC;AAChC,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,oBAAoB;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,CAAC,MAAM,SAAS,GAAwB;IAC5C,eAAe;IACf,cAAc;IACd,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,gBAAgB;IAChB,cAAc;IACd,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,cAAc;IACd,gBAAgB;IAChB,uBAAuB;IACvB,YAAY;IACZ,gCAAgC;IAChC,0BAA0B;IAC1B,iBAAiB;IACjB,oBAAoB;IACpB,eAAe;IACf,eAAe;IACf,eAAe;IACf,gBAAgB;CACjB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,KAA0B;IACxD,MAAM,CAAC,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,eAAe;AACf,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,gCAAgC;AAChC,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,oBAAoB;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,CAAC,MAAM,SAAS,GAAwB;IAC5C,eAAe;IACf,cAAc;IACd,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,gBAAgB;IAChB,cAAc;IACd,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,cAAc;IACd,gBAAgB;IAChB,uBAAuB;IACvB,YAAY;IACZ,gCAAgC;IAChC,0BAA0B;IAC1B,iBAAiB;IACjB,oBAAoB;IACpB,eAAe;IACf,eAAe;IACf,eAAe;IACf,gBAAgB;CACjB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,KAA0B;IACxD,MAAM,CAAC,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -30,11 +30,22 @@ export interface ToolDefinition {
30
30
  * to recipients, deletions without a deterministic restore path,
31
31
  * payments). MUST gate via the two-phase confirmation token pattern
32
32
  * (preview → token → execute) per ADR 0002. CI enforces this.
33
+ * - `permission_mutating` — operations that create/modify standing
34
+ * access, sharing, or ownership of a resource (sharing-link creation,
35
+ * guest invite, permission grant/revoke, sensitivity-label change,
36
+ * ownership transfer). Distinct from `write_irreversible` because
37
+ * the threat model is privilege escalation: a single call grants a
38
+ * third party persistent access that survives the session and
39
+ * bypasses content-level RW deny-lists. No tool in v0.4.0 carries
40
+ * this category — it is a pre-classification slot. CI Layer A
41
+ * (`.github/workflows/ci.yml`) fails the build if any future tool
42
+ * touches a permission/sharing Graph endpoint without being
43
+ * classified here AND added to the workflow's allowlist.
33
44
  *
34
45
  * Ambiguous categorizations resolve to the strictest. When in doubt,
35
46
  * gate it.
36
47
  */
37
- export type ToolCategory = "read" | "write_idempotent" | "write_irreversible";
48
+ export type ToolCategory = "read" | "write_idempotent" | "write_irreversible" | "permission_mutating";
38
49
  /**
39
50
  * Return shape for a tool handler. We use the SDK's `CallToolResult`
40
51
  * directly so the dispatcher in `src/index.ts` can return handler
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/types/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;AAE3B,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,WAAW,CAAC;CACtB"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/types/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,kBAAkB,GAClB,oBAAoB,GACpB,qBAAqB,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;AAE3B,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,WAAW,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juvantlabs/m365-graph-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Microsoft Graph MCP server — OneDrive, SharePoint, and Calendar read+write for Juvant OS agents.",
5
5
  "license": "MIT",
6
6
  "author": "Juvant Srls",