@matimo/microsoft 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.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @matimo/microsoft
2
+
3
+ Microsoft Graph tools for Matimo — search, OneDrive/SharePoint files, Outlook mail,
4
+ Microsoft Teams, calendar, and SharePoint publishing through YAML-defined tools that
5
+ work with any AI framework.
6
+
7
+ ## 📦 Installation
8
+
9
+ ```bash
10
+ npm install @matimo/microsoft
11
+ # or
12
+ pnpm add @matimo/microsoft
13
+ ```
14
+
15
+ ## 🚀 Quick Start
16
+
17
+ ```typescript
18
+ import { MatimoInstance } from '@matimo/core';
19
+
20
+ const matimo = await MatimoInstance.init('./packages/microsoft/tools');
21
+
22
+ // Search across SharePoint and OneDrive
23
+ const search = await matimo.execute('ms_search_knowledge', {
24
+ query: 'Q3 budget filetype:xlsx',
25
+ top: 5,
26
+ });
27
+
28
+ // List files in a OneDrive folder
29
+ const files = await matimo.execute('ms_list_files', {
30
+ drive_id: 'b!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
31
+ });
32
+
33
+ // Read a plain-text file's contents
34
+ const file = await matimo.execute('ms_read_file', {
35
+ drive_id: 'b!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
36
+ item_id: '01ABCXYZ7654321',
37
+ });
38
+
39
+ // Send an email (requires_approval: true — routed through HITL)
40
+ await matimo.execute('ms_send_email', {
41
+ to: ['alice@contoso.com'],
42
+ subject: 'Weekly status update',
43
+ body: 'Here is the summary for this week...',
44
+ });
45
+ ```
46
+
47
+ ## 🛠️ Available Tools
48
+
49
+ | Tool | Description | Risk | Graph endpoint |
50
+ |------|-------------|------|----------------|
51
+ | `ms_search_knowledge` | Search SharePoint sites, OneDrive/SharePoint files, and list items | low | `POST /search/query` |
52
+ | `ms_read_file` | Read a OneDrive/SharePoint file's contents (plain-text formats only) | low | `GET /drives/{id}/items/{id}/content` |
53
+ | `ms_list_files` | List the children of a OneDrive/SharePoint folder | low | `GET /drives/{id}/items/{id}/children` |
54
+ | `ms_get_email` | List messages in the signed-in user's mailbox | low | `GET /me/messages` |
55
+ | `ms_send_email` | Send an email as the signed-in user | **high** (approval) | `POST /me/messages` + `/send` |
56
+ | `ms_send_teams_message` | Post (or reply to) a message in a Teams channel | medium | `POST /teams/{id}/channels/{id}/messages` |
57
+ | `ms_create_document` | Upload a small file to OneDrive/SharePoint (≤4 MB) | medium | `PUT /drives/{id}/items/{id}:/{name}:/content` |
58
+ | `ms_create_calendar_event` | Create a calendar event, optionally as a Teams meeting | medium | `POST /me/events` |
59
+ | `ms_publish_to_sharepoint` | Create and publish a SharePoint site page | **high** (approval) | `POST /sites/{id}/pages` + `/publish` |
60
+
61
+ ## 🔐 Authentication
62
+
63
+ Microsoft Graph tools use delegated OAuth2 access tokens. Matimo never performs the
64
+ OAuth code exchange itself — connect Microsoft through your Matimo deployment (Nova),
65
+ then provide the resulting token at execution time:
66
+
67
+ ```bash
68
+ export MICROSOFT_GRAPH_ACCESS_TOKEN="eyJ0eXAiOiJKV1Qi..."
69
+ ```
70
+
71
+ or pass it through per-call credentials:
72
+
73
+ ```typescript
74
+ await matimo.execute(
75
+ 'ms_get_email',
76
+ { top: 5 },
77
+ { credentials: { MICROSOFT_GRAPH_ACCESS_TOKEN: token } }
78
+ );
79
+ ```
80
+
81
+ See [`definition.yaml`](./definition.yaml) for the full OAuth2 provider configuration
82
+ (authorization/token endpoints, default scopes, and app registration setup steps).
83
+
84
+ ## ⚠️ Risk & Approval
85
+
86
+ `ms_send_email` and `ms_publish_to_sharepoint` are marked `risk: high` and
87
+ `requires_approval: true` — Matimo routes them through the human-in-the-loop approval
88
+ flow before they execute, since they send mail and publish content visible to others
89
+ on the user's behalf. `ms_send_teams_message`, `ms_create_document`, and
90
+ `ms_create_calendar_event` are `risk: medium` (external writes, narrower blast radius).
91
+ The remaining read-only tools are `risk: low`.
92
+
93
+ ## 📚 Integration Examples
94
+
95
+ See [`examples/tools/microsoft/`](../../examples/tools/microsoft/) for runnable
96
+ factory, decorator, LangChain agent, and policy-approval examples.
97
+
98
+ ## Additional Resources
99
+
100
+ - [Microsoft Graph API overview](https://learn.microsoft.com/en-us/graph/overview)
101
+ - [Microsoft Graph permissions reference](https://learn.microsoft.com/en-us/graph/permissions-reference)
102
+ - [Microsoft Entra admin center](https://entra.microsoft.com) (app registration)
103
+ - [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) (try API calls interactively)
104
+ - [Matimo Documentation](../../README.md)
@@ -0,0 +1,61 @@
1
+ # Microsoft Identity Platform OAuth2 Provider Definition
2
+ #
3
+ # This file defines the OAuth2 configuration for Microsoft (Entra ID / Azure AD v2.0).
4
+ # All Microsoft Graph tools reference this provider definition.
5
+ #
6
+ # Users can override these endpoints via:
7
+ # 1. Runtime config (highest priority)
8
+ # 2. Environment variables: OAUTH_MICROSOFT_AUTH_URL, OAUTH_MICROSOFT_TOKEN_URL, etc.
9
+ # 3. This YAML definition (lowest priority)
10
+ #
11
+ # Pattern: Define once, use everywhere
12
+
13
+ name: microsoft-provider
14
+ type: provider
15
+ version: '1.0.0'
16
+
17
+ description: |
18
+ Microsoft Identity Platform OAuth2 Provider Configuration
19
+
20
+ All Microsoft Graph tools use these endpoints to obtain delegated
21
+ access tokens for the Graph API (https://graph.microsoft.com).
22
+
23
+ Setup:
24
+ 1. Register an application in the Microsoft Entra admin center (https://entra.microsoft.com)
25
+ 2. Add a Web/SPA redirect URI for your OAuth callback
26
+ 3. Create a client secret (or configure a certificate) under "Certificates & secrets"
27
+ 4. Grant the delegated Graph scopes your tools need under "API permissions"
28
+ 5. Set MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET environment variables
29
+ 6. Set MICROSOFT_REDIRECT_URI to your callback URL
30
+
31
+ Note: Matimo never performs the OAuth code exchange itself — it expects a
32
+ valid delegated access token to be supplied at execution time (e.g. via
33
+ MICROSOFT_GRAPH_ACCESS_TOKEN), exactly like the Slack/Gmail providers.
34
+
35
+ provider:
36
+ name: microsoft
37
+ displayName: Microsoft
38
+
39
+ # OAuth2 Endpoints (Microsoft identity platform v2.0, multi-tenant "common" tenant)
40
+ # See: https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oauth-code
41
+ endpoints:
42
+ authorizationUrl: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
43
+ tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token
44
+ revokeUrl: https://login.microsoftonline.com/common/oauth2/v2.0/logout
45
+
46
+ # Standard delegated scopes for the tools in this package.
47
+ # Tools can request a narrower set with their own `authentication.scopes`.
48
+ defaultScopes:
49
+ - offline_access
50
+ - https://graph.microsoft.com/Sites.Read.All
51
+ - https://graph.microsoft.com/Files.Read.All
52
+ - https://graph.microsoft.com/Mail.Read
53
+ - https://graph.microsoft.com/Mail.Send
54
+ - https://graph.microsoft.com/ChannelMessage.Send
55
+ - https://graph.microsoft.com/Files.ReadWrite
56
+ - https://graph.microsoft.com/Calendars.ReadWrite
57
+ - https://graph.microsoft.com/Sites.Manage.All
58
+
59
+ # Additional metadata
60
+ documentation: https://learn.microsoft.com/en-us/graph/overview
61
+ learnMore: https://developer.microsoft.com/en-us/graph/graph-explorer
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@matimo/microsoft",
3
+ "version": "0.1.0",
4
+ "description": "Microsoft Graph tools for Matimo (search, files, mail, Teams, calendar, SharePoint)",
5
+ "type": "module",
6
+ "files": [
7
+ "tools",
8
+ "README.md",
9
+ "definition.yaml"
10
+ ],
11
+ "peerDependencies": {
12
+ "matimo": "0.1.3"
13
+ },
14
+ "devDependencies": {
15
+ "axios": "^1.15.2",
16
+ "@matimo/core": "0.1.3"
17
+ }
18
+ }
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Shared Microsoft Graph helpers for all `type: function` tools in this package.
3
+ *
4
+ * Conventions (mirrors @matimo/slack and @matimo/gmail):
5
+ * - Tools NEVER perform OAuth token exchange. A delegated Graph access token is
6
+ * injected at execution time via `context.credentials.MICROSOFT_GRAPH_ACCESS_TOKEN`
7
+ * (or the MICROSOFT_GRAPH_ACCESS_TOKEN environment variable as a fallback).
8
+ * - Every Graph error is normalized into a MatimoError with the closest matching
9
+ * ErrorCode (Matimo has no per-provider error classes — see errors/matimo-error.ts).
10
+ */
11
+ import axios from 'axios';
12
+ import { MatimoError, ErrorCode } from '@matimo/core';
13
+
14
+ export const GRAPH_BASE_URL = 'https://graph.microsoft.com/v1.0';
15
+
16
+ export interface ToolContext {
17
+ credentials?: Record<string, string>;
18
+ }
19
+
20
+ const RETRYABLE_STATUS_CODES = new Set([429, 500, 503]);
21
+ const MAX_RETRIES = 3;
22
+ const INITIAL_BACKOFF_MS = 500;
23
+
24
+ /**
25
+ * Resolve the delegated Graph access token. Matimo never exchanges OAuth codes —
26
+ * the token must already be present in per-call credentials or the environment.
27
+ */
28
+ export function getAccessToken(context?: ToolContext): string {
29
+ const token =
30
+ context?.credentials?.MICROSOFT_GRAPH_ACCESS_TOKEN ?? process.env.MICROSOFT_GRAPH_ACCESS_TOKEN;
31
+
32
+ if (!token) {
33
+ throw new MatimoError(
34
+ 'Microsoft Graph access token is missing. Provide it via credentials.MICROSOFT_GRAPH_ACCESS_TOKEN ' +
35
+ 'or the MICROSOFT_GRAPH_ACCESS_TOKEN environment variable. Matimo never performs the OAuth ' +
36
+ 'exchange itself — connect Microsoft in Nova first.',
37
+ ErrorCode.AUTH_FAILED,
38
+ { provider: 'microsoft', placeholder: 'MICROSOFT_GRAPH_ACCESS_TOKEN' }
39
+ );
40
+ }
41
+
42
+ return token;
43
+ }
44
+
45
+ /**
46
+ * Validate required parameters BEFORE any network call, mirroring the
47
+ * "ValidationError before any API call" requirement. Throws VALIDATION_FAILED.
48
+ */
49
+ export function requireParams(
50
+ params: Record<string, unknown>,
51
+ required: string[],
52
+ toolName: string
53
+ ): void {
54
+ const missing = required.filter((name) => {
55
+ const value = params[name];
56
+ return value === undefined || value === null || value === '';
57
+ });
58
+
59
+ if (missing.length > 0) {
60
+ throw new MatimoError(
61
+ `${toolName}: missing required parameter(s): ${missing.join(', ')}`,
62
+ ErrorCode.VALIDATION_FAILED,
63
+ { toolName, missingParams: missing }
64
+ );
65
+ }
66
+ }
67
+
68
+ interface GraphErrorBody {
69
+ error?: { code?: string; message?: string };
70
+ }
71
+
72
+ /**
73
+ * Map a Microsoft Graph HTTP error response onto a MatimoError using the closest
74
+ * matching ErrorCode (Matimo has no CredentialError/NotFoundError/ProviderError
75
+ * classes — see typescript/packages/core/src/errors/matimo-error.ts):
76
+ * 401/403 -> AUTH_FAILED ("Microsoft Graph access denied. Check connection status in Nova.")
77
+ * 404 -> FILE_NOT_FOUND (details.resourceType identifies what was missing)
78
+ * 429 -> RATE_LIMIT_EXCEEDED (details.retryAfterSeconds carries Retry-After)
79
+ * 500/503 -> EXECUTION_FAILED (retryable)
80
+ * other -> EXECUTION_FAILED
81
+ */
82
+ export function mapGraphError(
83
+ status: number,
84
+ data: unknown,
85
+ headers: Record<string, unknown> | undefined,
86
+ resourceType: string
87
+ ): MatimoError {
88
+ const graphError = (data as GraphErrorBody | undefined)?.error;
89
+ const details: Record<string, unknown> = { statusCode: status, graphError, resourceType };
90
+
91
+ if (status === 401 || status === 403) {
92
+ return new MatimoError(
93
+ 'Microsoft Graph access denied. Check connection status in Nova.',
94
+ ErrorCode.AUTH_FAILED,
95
+ details
96
+ );
97
+ }
98
+
99
+ if (status === 404) {
100
+ return new MatimoError(`${resourceType} not found.`, ErrorCode.FILE_NOT_FOUND, details);
101
+ }
102
+
103
+ if (status === 429) {
104
+ const retryAfterHeader = headers?.['retry-after'] ?? headers?.['Retry-After'];
105
+ const retryAfterSeconds =
106
+ retryAfterHeader !== undefined ? Number(retryAfterHeader) : undefined;
107
+ return new MatimoError(
108
+ 'Microsoft Graph rate limit exceeded. Respect Retry-After before retrying.',
109
+ ErrorCode.RATE_LIMIT_EXCEEDED,
110
+ { ...details, retryAfterSeconds }
111
+ );
112
+ }
113
+
114
+ if (status === 500 || status === 503) {
115
+ return new MatimoError(
116
+ 'Microsoft Graph service is temporarily unavailable. Please retry shortly.',
117
+ ErrorCode.EXECUTION_FAILED,
118
+ details
119
+ );
120
+ }
121
+
122
+ return new MatimoError(
123
+ `Microsoft Graph request failed with status ${status}.`,
124
+ ErrorCode.EXECUTION_FAILED,
125
+ details
126
+ );
127
+ }
128
+
129
+ function buildQueryString(query?: Record<string, string | number | undefined>): string {
130
+ if (!query) return '';
131
+ const parts = Object.entries(query)
132
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
133
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
134
+ return parts.length > 0 ? `?${parts.join('&')}` : '';
135
+ }
136
+
137
+ function sleep(ms: number): Promise<void> {
138
+ return new Promise((resolve) => setTimeout(resolve, ms));
139
+ }
140
+
141
+ export interface GraphRequestOptions {
142
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
143
+ /** Path relative to https://graph.microsoft.com/v1.0, e.g. '/me/messages' */
144
+ path: string;
145
+ token: string;
146
+ query?: Record<string, string | number | undefined>;
147
+ body?: unknown;
148
+ headers?: Record<string, string>;
149
+ /** Human-readable resource name used to build a clear 404 message, e.g. 'Drive item' */
150
+ resourceType?: string;
151
+ /** Set to 'arraybuffer' for binary downloads (e.g. file content) */
152
+ responseType?: 'json' | 'arraybuffer';
153
+ /** Treat a 204/empty body as success and return null (e.g. publish, sendMail) */
154
+ allowEmptyResponse?: boolean;
155
+ }
156
+
157
+ /**
158
+ * Perform an authenticated Microsoft Graph request with retry-on-429/5xx
159
+ * (respecting Retry-After, exponential backoff, max 3 retries) and normalized
160
+ * MatimoError mapping for every other failure.
161
+ */
162
+ export async function graphRequest<T = unknown>(options: GraphRequestOptions): Promise<T> {
163
+ const {
164
+ method,
165
+ path,
166
+ token,
167
+ query,
168
+ body,
169
+ headers,
170
+ resourceType = 'Resource',
171
+ responseType = 'json',
172
+ allowEmptyResponse = false,
173
+ } = options;
174
+
175
+ const url = `${GRAPH_BASE_URL}${path}${buildQueryString(query)}`;
176
+
177
+ let attempt = 0;
178
+ for (;;) {
179
+ let response;
180
+ try {
181
+ response = await axios.request({
182
+ method,
183
+ url,
184
+ data: body,
185
+ responseType,
186
+ validateStatus: () => true,
187
+ headers: {
188
+ Authorization: `Bearer ${token}`,
189
+ ...(responseType === 'json' ? { Accept: 'application/json' } : {}),
190
+ ...(body !== undefined && !(body instanceof Buffer)
191
+ ? { 'Content-Type': 'application/json' }
192
+ : {}),
193
+ ...headers,
194
+ },
195
+ });
196
+ } catch (error) {
197
+ throw new MatimoError(
198
+ 'Microsoft Graph request failed before a response was received (network error).',
199
+ ErrorCode.NETWORK_ERROR,
200
+ { path, originalError: error instanceof Error ? error.message : String(error) },
201
+ error
202
+ );
203
+ }
204
+
205
+ if (response.status >= 200 && response.status < 300) {
206
+ if (allowEmptyResponse && (response.status === 204 || !response.data)) {
207
+ return null as T;
208
+ }
209
+ return response.data as T;
210
+ }
211
+
212
+ const error = mapGraphError(
213
+ response.status,
214
+ response.data,
215
+ response.headers as Record<string, unknown>,
216
+ resourceType
217
+ );
218
+
219
+ const isRetryable = RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES;
220
+ if (!isRetryable) {
221
+ throw error;
222
+ }
223
+
224
+ const retryAfterSeconds = error.details?.retryAfterSeconds as number | undefined;
225
+ const delayMs =
226
+ retryAfterSeconds !== undefined && !Number.isNaN(retryAfterSeconds)
227
+ ? retryAfterSeconds * 1000
228
+ : INITIAL_BACKOFF_MS * 2 ** attempt;
229
+
230
+ attempt += 1;
231
+ await sleep(delayMs);
232
+ }
233
+ }
@@ -0,0 +1,100 @@
1
+ name: ms_create_calendar_event
2
+ description: |
3
+ Create an event on the signed-in user's default calendar (POST /me/events).
4
+ Supports attendees, location, a single IANA/Windows time zone for both start and
5
+ end, and Microsoft Teams online meetings (isOnlineMeeting), returning the meeting's
6
+ join URL when one is created.
7
+ version: '1.0.0'
8
+ status: approved
9
+ risk: medium
10
+
11
+ parameters:
12
+ subject:
13
+ type: string
14
+ description: Title of the event
15
+ required: true
16
+
17
+ body:
18
+ type: string
19
+ description: Description / agenda for the event (plain text)
20
+ required: false
21
+
22
+ start:
23
+ type: string
24
+ description: 'Start date and time, e.g. "2026-06-15T09:00:00"'
25
+ required: true
26
+
27
+ end:
28
+ type: string
29
+ description: 'End date and time, e.g. "2026-06-15T09:30:00"'
30
+ required: true
31
+
32
+ timezone:
33
+ type: string
34
+ description: 'IANA or Windows time zone applied to both `start` and `end`, e.g. "America/Los_Angeles"'
35
+ required: false
36
+ default: UTC
37
+
38
+ attendees:
39
+ type: array
40
+ description: Email addresses of attendees to invite
41
+ required: false
42
+
43
+ location:
44
+ type: string
45
+ description: Free-text location / room name for the event
46
+ required: false
47
+
48
+ is_online_meeting:
49
+ type: boolean
50
+ description: When true, Microsoft Teams creates an online meeting for this event
51
+ required: false
52
+ default: false
53
+
54
+ execution:
55
+ type: function
56
+ code: ms_create_calendar_event.ts
57
+ timeout: 25000
58
+
59
+ authentication:
60
+ type: oauth2
61
+ provider: microsoft
62
+ scopes:
63
+ - https://graph.microsoft.com/Calendars.ReadWrite
64
+
65
+ output_schema:
66
+ type: object
67
+ properties:
68
+ success:
69
+ type: boolean
70
+ event_id:
71
+ type: string
72
+ web_link:
73
+ type: string
74
+ join_url:
75
+ type: string
76
+ description: Microsoft Teams meeting join URL — present only when is_online_meeting is true
77
+
78
+ error_handling:
79
+ retry: 1
80
+ backoff_type: exponential
81
+ initial_delay_ms: 1000
82
+
83
+ tags: [microsoft, graph, calendar, outlook, teams, write]
84
+
85
+ examples:
86
+ - name: Schedule a 30-minute internal sync
87
+ params:
88
+ subject: 'Sprint planning'
89
+ start: '2026-06-15T09:00:00'
90
+ end: '2026-06-15T09:30:00'
91
+ timezone: 'America/Los_Angeles'
92
+ attendees: ['alice@contoso.com', 'bob@contoso.com']
93
+ - name: Schedule an online Teams meeting with a location fallback
94
+ params:
95
+ subject: 'All-hands'
96
+ start: '2026-06-20T17:00:00'
97
+ end: '2026-06-20T18:00:00'
98
+ timezone: UTC
99
+ location: 'Building 4, Room 200'
100
+ is_online_meeting: true
@@ -0,0 +1,79 @@
1
+ /**
2
+ * ms_create_calendar_event — POST /me/events
3
+ * https://learn.microsoft.com/en-us/graph/api/user-post-events
4
+ */
5
+ import { MatimoError, ErrorCode } from '@matimo/core';
6
+ import { getAccessToken, requireParams, graphRequest, type ToolContext } from '../graph-client';
7
+
8
+ const DEFAULT_TIMEZONE = 'UTC';
9
+
10
+ interface OnlineMeeting {
11
+ joinUrl?: string;
12
+ }
13
+
14
+ interface CalendarEvent {
15
+ id?: string;
16
+ webLink?: string;
17
+ onlineMeeting?: OnlineMeeting;
18
+ }
19
+
20
+ function toAttendeeList(value: unknown): Array<{ emailAddress: { address: string }; type: 'required' }> {
21
+ if (value === undefined) return [];
22
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string' || !entry)) {
23
+ throw new MatimoError(
24
+ "ms_create_calendar_event: 'attendees' must be an array of email address strings",
25
+ ErrorCode.VALIDATION_FAILED,
26
+ { attendees: value }
27
+ );
28
+ }
29
+ return (value as string[]).map((address) => ({
30
+ emailAddress: { address },
31
+ type: 'required' as const,
32
+ }));
33
+ }
34
+
35
+ export default async function execute(
36
+ params: Record<string, unknown>,
37
+ context?: ToolContext
38
+ ): Promise<unknown> {
39
+ requireParams(params, ['subject', 'start', 'end'], 'ms_create_calendar_event');
40
+
41
+ const subject = String(params.subject);
42
+ const start = String(params.start);
43
+ const end = String(params.end);
44
+ const timezone =
45
+ typeof params.timezone === 'string' && params.timezone ? params.timezone : DEFAULT_TIMEZONE;
46
+
47
+ const attendees = toAttendeeList(params.attendees);
48
+ const isOnlineMeeting = params.is_online_meeting === true;
49
+
50
+ const token = getAccessToken(context);
51
+
52
+ const event = await graphRequest<CalendarEvent>({
53
+ method: 'POST',
54
+ path: '/me/events',
55
+ token,
56
+ resourceType: 'Calendar',
57
+ body: {
58
+ subject,
59
+ ...(typeof params.body === 'string' && params.body
60
+ ? { body: { contentType: 'Text', content: params.body } }
61
+ : {}),
62
+ start: { dateTime: start, timeZone: timezone },
63
+ end: { dateTime: end, timeZone: timezone },
64
+ ...(attendees.length > 0 ? { attendees } : {}),
65
+ ...(typeof params.location === 'string' && params.location
66
+ ? { location: { displayName: params.location } }
67
+ : {}),
68
+ isOnlineMeeting,
69
+ ...(isOnlineMeeting ? { onlineMeetingProvider: 'teamsForBusiness' } : {}),
70
+ },
71
+ });
72
+
73
+ return {
74
+ success: true,
75
+ event_id: event?.id ?? '',
76
+ web_link: event?.webLink ?? '',
77
+ ...(event?.onlineMeeting?.joinUrl ? { join_url: event.onlineMeeting.joinUrl } : {}),
78
+ };
79
+ }
@@ -0,0 +1,103 @@
1
+ name: ms_create_document
2
+ description: |
3
+ Create (or overwrite) a small file in OneDrive or a SharePoint document library by
4
+ uploading content directly
5
+ (PUT /drives/{drive_id}/items/{parent_item_id}:/{filename}:/content). Intended for
6
+ small text-based documents — Microsoft Graph requires resumable upload sessions for
7
+ files larger than 4 MB, which this tool does not implement.
8
+ version: '1.0.0'
9
+ status: approved
10
+ risk: medium
11
+
12
+ parameters:
13
+ drive_id:
14
+ type: string
15
+ description: ID of the destination drive (OneDrive or SharePoint document library)
16
+ required: true
17
+
18
+ parent_item_id:
19
+ type: string
20
+ description: ID of the destination folder item. Defaults to the drive's root folder.
21
+ required: false
22
+ default: root
23
+
24
+ filename:
25
+ type: string
26
+ description: 'Name to give the new file, including extension, e.g. "report.md"'
27
+ required: true
28
+
29
+ content:
30
+ type: string
31
+ description: File content. Provide as plain text, or as base64 when content_encoding is "base64"
32
+ required: true
33
+
34
+ content_encoding:
35
+ type: string
36
+ description: How `content` is encoded
37
+ required: false
38
+ default: text
39
+ enum: [text, base64]
40
+
41
+ conflict_behaviour:
42
+ type: string
43
+ description: >-
44
+ How to handle a name collision with an existing item. Passed as the
45
+ @microsoft.graph.conflictBehavior query hint on a best-effort basis — Graph's
46
+ simple-upload (PUT .../content) endpoint documents this parameter primarily for
47
+ resumable upload sessions, so behaviour may vary by tenant/library configuration.
48
+ required: false
49
+ default: replace
50
+ enum: [replace, rename, fail]
51
+
52
+ execution:
53
+ type: function
54
+ code: ms_create_document.ts
55
+ timeout: 30000
56
+
57
+ authentication:
58
+ type: oauth2
59
+ provider: microsoft
60
+ scopes:
61
+ - https://graph.microsoft.com/Files.ReadWrite
62
+
63
+ output_schema:
64
+ type: object
65
+ properties:
66
+ success:
67
+ type: boolean
68
+ item_id:
69
+ type: string
70
+ name:
71
+ type: string
72
+ web_url:
73
+ type: string
74
+ size_bytes:
75
+ type: number
76
+
77
+ error_handling:
78
+ retry: 1
79
+ backoff_type: exponential
80
+ initial_delay_ms: 1000
81
+
82
+ tags: [microsoft, graph, files, onedrive, sharepoint, write]
83
+
84
+ examples:
85
+ - name: Create a Markdown summary document at the drive root
86
+ params:
87
+ drive_id: 'b!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
88
+ filename: 'meeting-notes-2026-06-07.md'
89
+ content: '# Meeting notes\n\n- Decided to ship v2.4.0 next week'
90
+ - name: Upload a base64-encoded file into a specific folder
91
+ params:
92
+ drive_id: 'b!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
93
+ parent_item_id: '01ABCXYZ7654321'
94
+ filename: 'export.csv'
95
+ content: 'bmFtZSxlbWFpbAphbGljZSxhbGljZUBjb250b3NvLmNvbQ=='
96
+ content_encoding: base64
97
+ conflict_behaviour: rename
98
+
99
+ notes:
100
+ caution: >-
101
+ Uses the simple-upload endpoint, which Microsoft Graph limits to files up to
102
+ 4 MB. Larger files require a resumable upload session
103
+ (createUploadSession), which this tool does not implement.