@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 +104 -0
- package/definition.yaml +61 -0
- package/package.json +18 -0
- package/tools/graph-client.ts +233 -0
- package/tools/ms_create_calendar_event/definition.yaml +100 -0
- package/tools/ms_create_calendar_event/ms_create_calendar_event.ts +79 -0
- package/tools/ms_create_document/definition.yaml +103 -0
- package/tools/ms_create_document/ms_create_document.ts +96 -0
- package/tools/ms_get_email/definition.yaml +88 -0
- package/tools/ms_get_email/ms_get_email.ts +94 -0
- package/tools/ms_list_files/definition.yaml +81 -0
- package/tools/ms_list_files/ms_list_files.ts +71 -0
- package/tools/ms_publish_to_sharepoint/definition.yaml +92 -0
- package/tools/ms_publish_to_sharepoint/ms_publish_to_sharepoint.ts +126 -0
- package/tools/ms_read_file/definition.yaml +74 -0
- package/tools/ms_read_file/ms_read_file.ts +102 -0
- package/tools/ms_search_knowledge/definition.yaml +99 -0
- package/tools/ms_search_knowledge/ms_search_knowledge.ts +109 -0
- package/tools/ms_send_email/definition.yaml +94 -0
- package/tools/ms_send_email/ms_send_email.ts +98 -0
- package/tools/ms_send_teams_message/definition.yaml +87 -0
- package/tools/ms_send_teams_message/ms_send_teams_message.ts +69 -0
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)
|
package/definition.yaml
ADDED
|
@@ -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.
|