@zapier/microsoft-outlook-connector 0.0.0 → 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/LICENSE +93 -0
- package/NOTICE +8 -0
- package/NOTICE.md +8 -0
- package/README.md +145 -2
- package/SKILL.md +159 -0
- package/cli.js +71 -0
- package/cli.ts +5 -0
- package/connections.ts +69 -0
- package/dist/cli.js +4 -0
- package/dist/index.js +1823 -0
- package/index.ts +103 -0
- package/package.json +65 -4
- package/preflight.sh +157 -0
- package/references/microsoft-outlook-api-gotchas.md +210 -0
- package/scripts/copyMessage.ts +68 -0
- package/scripts/createContact.ts +39 -0
- package/scripts/createDraft.ts +71 -0
- package/scripts/createEvent.ts +61 -0
- package/scripts/createMailFolder.ts +68 -0
- package/scripts/createReplyDraft.ts +81 -0
- package/scripts/deleteContact.ts +47 -0
- package/scripts/deleteEvent.ts +61 -0
- package/scripts/deleteMessage.ts +57 -0
- package/scripts/forwardMessage.ts +75 -0
- package/scripts/getAttachment.ts +60 -0
- package/scripts/getContact.ts +44 -0
- package/scripts/getEvent.ts +63 -0
- package/scripts/getMe.ts +42 -0
- package/scripts/getMessage.ts +68 -0
- package/scripts/listAttachments.ts +94 -0
- package/scripts/listCalendarView.ts +99 -0
- package/scripts/listCalendars.ts +85 -0
- package/scripts/listCategories.ts +49 -0
- package/scripts/listContacts.ts +81 -0
- package/scripts/listEvents.ts +94 -0
- package/scripts/listMailFolders.ts +98 -0
- package/scripts/listMessages.ts +106 -0
- package/scripts/moveMessage.ts +68 -0
- package/scripts/replyToMessage.ts +73 -0
- package/scripts/sendDraft.ts +55 -0
- package/scripts/sendMail.ts +68 -0
- package/scripts/updateContact.ts +49 -0
- package/scripts/updateEvent.ts +69 -0
- package/scripts/updateMessage.ts +99 -0
- package/tsup.config.ts +63 -0
package/index.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { defineConnector, toFunctions } from "@zapier/connectors-sdk";
|
|
2
|
+
|
|
3
|
+
import { connectionResolvers } from "./connections.ts";
|
|
4
|
+
import copyMessageDefinition from "./scripts/copyMessage.ts";
|
|
5
|
+
import createContactDefinition from "./scripts/createContact.ts";
|
|
6
|
+
import createDraftDefinition from "./scripts/createDraft.ts";
|
|
7
|
+
import createEventDefinition from "./scripts/createEvent.ts";
|
|
8
|
+
import createMailFolderDefinition from "./scripts/createMailFolder.ts";
|
|
9
|
+
import createReplyDraftDefinition from "./scripts/createReplyDraft.ts";
|
|
10
|
+
import deleteContactDefinition from "./scripts/deleteContact.ts";
|
|
11
|
+
import deleteEventDefinition from "./scripts/deleteEvent.ts";
|
|
12
|
+
import deleteMessageDefinition from "./scripts/deleteMessage.ts";
|
|
13
|
+
import forwardMessageDefinition from "./scripts/forwardMessage.ts";
|
|
14
|
+
import getAttachmentDefinition from "./scripts/getAttachment.ts";
|
|
15
|
+
import getContactDefinition from "./scripts/getContact.ts";
|
|
16
|
+
import getEventDefinition from "./scripts/getEvent.ts";
|
|
17
|
+
import getMeDefinition from "./scripts/getMe.ts";
|
|
18
|
+
import getMessageDefinition from "./scripts/getMessage.ts";
|
|
19
|
+
import listAttachmentsDefinition from "./scripts/listAttachments.ts";
|
|
20
|
+
import listCalendarsDefinition from "./scripts/listCalendars.ts";
|
|
21
|
+
import listCalendarViewDefinition from "./scripts/listCalendarView.ts";
|
|
22
|
+
import listCategoriesDefinition from "./scripts/listCategories.ts";
|
|
23
|
+
import listContactsDefinition from "./scripts/listContacts.ts";
|
|
24
|
+
import listEventsDefinition from "./scripts/listEvents.ts";
|
|
25
|
+
import listMailFoldersDefinition from "./scripts/listMailFolders.ts";
|
|
26
|
+
import listMessagesDefinition from "./scripts/listMessages.ts";
|
|
27
|
+
import moveMessageDefinition from "./scripts/moveMessage.ts";
|
|
28
|
+
import replyToMessageDefinition from "./scripts/replyToMessage.ts";
|
|
29
|
+
import sendDraftDefinition from "./scripts/sendDraft.ts";
|
|
30
|
+
import sendMailDefinition from "./scripts/sendMail.ts";
|
|
31
|
+
import updateContactDefinition from "./scripts/updateContact.ts";
|
|
32
|
+
import updateEventDefinition from "./scripts/updateEvent.ts";
|
|
33
|
+
import updateMessageDefinition from "./scripts/updateMessage.ts";
|
|
34
|
+
|
|
35
|
+
const connector = defineConnector({
|
|
36
|
+
scripts: {
|
|
37
|
+
copyMessage: copyMessageDefinition,
|
|
38
|
+
createContact: createContactDefinition,
|
|
39
|
+
createDraft: createDraftDefinition,
|
|
40
|
+
createEvent: createEventDefinition,
|
|
41
|
+
createMailFolder: createMailFolderDefinition,
|
|
42
|
+
createReplyDraft: createReplyDraftDefinition,
|
|
43
|
+
deleteContact: deleteContactDefinition,
|
|
44
|
+
deleteEvent: deleteEventDefinition,
|
|
45
|
+
deleteMessage: deleteMessageDefinition,
|
|
46
|
+
forwardMessage: forwardMessageDefinition,
|
|
47
|
+
getAttachment: getAttachmentDefinition,
|
|
48
|
+
getContact: getContactDefinition,
|
|
49
|
+
getEvent: getEventDefinition,
|
|
50
|
+
getMe: getMeDefinition,
|
|
51
|
+
getMessage: getMessageDefinition,
|
|
52
|
+
listAttachments: listAttachmentsDefinition,
|
|
53
|
+
listCalendars: listCalendarsDefinition,
|
|
54
|
+
listCalendarView: listCalendarViewDefinition,
|
|
55
|
+
listCategories: listCategoriesDefinition,
|
|
56
|
+
listContacts: listContactsDefinition,
|
|
57
|
+
listEvents: listEventsDefinition,
|
|
58
|
+
listMailFolders: listMailFoldersDefinition,
|
|
59
|
+
listMessages: listMessagesDefinition,
|
|
60
|
+
moveMessage: moveMessageDefinition,
|
|
61
|
+
replyToMessage: replyToMessageDefinition,
|
|
62
|
+
sendDraft: sendDraftDefinition,
|
|
63
|
+
sendMail: sendMailDefinition,
|
|
64
|
+
updateContact: updateContactDefinition,
|
|
65
|
+
updateEvent: updateEventDefinition,
|
|
66
|
+
updateMessage: updateMessageDefinition,
|
|
67
|
+
},
|
|
68
|
+
connectionResolvers,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default connector;
|
|
72
|
+
export const {
|
|
73
|
+
copyMessage,
|
|
74
|
+
createContact,
|
|
75
|
+
createDraft,
|
|
76
|
+
createEvent,
|
|
77
|
+
createMailFolder,
|
|
78
|
+
createReplyDraft,
|
|
79
|
+
deleteContact,
|
|
80
|
+
deleteEvent,
|
|
81
|
+
deleteMessage,
|
|
82
|
+
forwardMessage,
|
|
83
|
+
getAttachment,
|
|
84
|
+
getContact,
|
|
85
|
+
getEvent,
|
|
86
|
+
getMe,
|
|
87
|
+
getMessage,
|
|
88
|
+
listAttachments,
|
|
89
|
+
listCalendars,
|
|
90
|
+
listCalendarView,
|
|
91
|
+
listCategories,
|
|
92
|
+
listContacts,
|
|
93
|
+
listEvents,
|
|
94
|
+
listMailFolders,
|
|
95
|
+
listMessages,
|
|
96
|
+
moveMessage,
|
|
97
|
+
replyToMessage,
|
|
98
|
+
sendDraft,
|
|
99
|
+
sendMail,
|
|
100
|
+
updateContact,
|
|
101
|
+
updateEvent,
|
|
102
|
+
updateMessage,
|
|
103
|
+
} = toFunctions(connector);
|
package/package.json
CHANGED
|
@@ -1,7 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/microsoft-outlook-connector",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Agent-callable Microsoft Outlook tools — read and search mail, send/reply/forward, organize messages and folders, manage calendar events, and manage contacts. Use when the user mentions Outlook, Microsoft 365 mail/calendar/contacts, or wants to send, read, search, or organize email, schedule events, or look up contacts — even if they don't name Outlook explicitly.",
|
|
5
8
|
"license": "Elastic-2.0",
|
|
6
|
-
"
|
|
7
|
-
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./index.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"@zapier/microsoft-outlook-connector": "./cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/",
|
|
21
|
+
"cli.js",
|
|
22
|
+
"*.ts",
|
|
23
|
+
"scripts/",
|
|
24
|
+
"preflight.sh",
|
|
25
|
+
"SKILL.md",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"NOTICE.md",
|
|
29
|
+
"references/",
|
|
30
|
+
"NOTICE"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@zapier/connectors-sdk": "^0.1.0",
|
|
34
|
+
"zod": "^4.0.0",
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@zapier/zapier-sdk": ">=0.59.0 <1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"microsoft-outlook",
|
|
42
|
+
"outlook",
|
|
43
|
+
"email",
|
|
44
|
+
"calendar",
|
|
45
|
+
"contacts",
|
|
46
|
+
"microsoft-365",
|
|
47
|
+
"zapier",
|
|
48
|
+
"connector",
|
|
49
|
+
"tools",
|
|
50
|
+
"skills",
|
|
51
|
+
"mcp",
|
|
52
|
+
"agent",
|
|
53
|
+
"ai",
|
|
54
|
+
"automation"
|
|
55
|
+
],
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/zapier/connectors.git",
|
|
59
|
+
"directory": "apps/microsoft-outlook"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"tsup": "^8.0.0",
|
|
63
|
+
"typescript": "^5.0.0"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/preflight.sh
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Connector pre-flight readiness check.
|
|
3
|
+
#
|
|
4
|
+
# Managed by @zapier/connectors-dev — do not edit; synced byte-for-byte
|
|
5
|
+
# across every connector.
|
|
6
|
+
#
|
|
7
|
+
# Runs inside whatever agent harness installed the connector (Cursor, Claude
|
|
8
|
+
# Code, Codex, Gemini CLI, Goose, ...) — often a minimal container — and answers
|
|
9
|
+
# ONE question: how do I run the TypeScript scripts here? It picks a runtime —
|
|
10
|
+
# Node 22.18+ when it can already resolve the connector's deps, else an explicit
|
|
11
|
+
# install step (`npm install`, or `bun install` when only Bun is present) — and
|
|
12
|
+
# tells the agent the exact command to run (see EXIT CODES). When deps are
|
|
13
|
+
# missing it disambiguates the two sandbox failures that block an install: a
|
|
14
|
+
# read-only connector dir (must run unsandboxed / be granted write) vs. a
|
|
15
|
+
# blocked home dir (point the package cache inside this dir). Both surface as a
|
|
16
|
+
# misleading `EPERM`, so the recommendation names the actual fix.
|
|
17
|
+
#
|
|
18
|
+
# NEEDS_ACTION is a single self-verifying step (install a runtime / deps), not a
|
|
19
|
+
# loop: do it, then run a script. The action confirms its own success and the
|
|
20
|
+
# first `--help` run is the authoritative check, so re-running this pre-flight to
|
|
21
|
+
# reconfirm is optional, not required.
|
|
22
|
+
#
|
|
23
|
+
# THE AGENT CONTRACT IS THE STDOUT, NOT THIS HEADER. Agents don't read this file;
|
|
24
|
+
# they run it and parse the `PREFLIGHT_*` lines — each value starts with a stable
|
|
25
|
+
# token (parse as `KEY: (\w+)`), with an optional human gloss in parens, and
|
|
26
|
+
# `PREFLIGHT_RECOMMENDATION` is the one-line next step. SKILL.md "Step 0" is the
|
|
27
|
+
# agent-facing spec; this header is for maintainers of the canonical script.
|
|
28
|
+
#
|
|
29
|
+
# WHY POSIX sh (not bash)
|
|
30
|
+
# Minimal sandboxes often ship only BusyBox `sh` with no bash. This script runs
|
|
31
|
+
# unchanged under BusyBox sh, dash, and bash, and never hard-requires
|
|
32
|
+
# node/bun/npm — a missing runtime degrades to a NEEDS_ACTION instruction.
|
|
33
|
+
#
|
|
34
|
+
# EXIT CODES (the verdict; also emitted on PREFLIGHT_STATUS)
|
|
35
|
+
# 0 READY a runtime + deps are in place; run the scripts
|
|
36
|
+
# 1 NEEDS_ACTION perform the printed action (install runtime / deps), then
|
|
37
|
+
# run a script — re-running this check is optional
|
|
38
|
+
|
|
39
|
+
set -u
|
|
40
|
+
|
|
41
|
+
EXIT_READY=0
|
|
42
|
+
EXIT_NEEDS_ACTION=1
|
|
43
|
+
|
|
44
|
+
# Directory this script lives in — deps + scripts are resolved relative to it,
|
|
45
|
+
# not the caller's cwd, so `./preflight.sh` works from anywhere.
|
|
46
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
47
|
+
|
|
48
|
+
has() {
|
|
49
|
+
command -v "$1" >/dev/null 2>&1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Node >= 22.18 is the connector baseline (native .ts stripping). Anything older
|
|
53
|
+
# is treated as "no Node" so we fall back to Bun.
|
|
54
|
+
node_ge_2218() {
|
|
55
|
+
has node || return 1
|
|
56
|
+
v=$(node -v 2>/dev/null) || return 1
|
|
57
|
+
v=${v#v}
|
|
58
|
+
major=${v%%.*}
|
|
59
|
+
rest=${v#*.}
|
|
60
|
+
minor=${rest%%.*}
|
|
61
|
+
case "$major" in '' | *[!0-9]*) return 1 ;; esac
|
|
62
|
+
case "$minor" in '' | *[!0-9]*) minor=0 ;; esac
|
|
63
|
+
[ "$major" -gt 22 ] && return 0
|
|
64
|
+
[ "$major" -eq 22 ] && [ "$minor" -ge 18 ] && return 0
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Are the connector's declared deps installed where Node would find them? Node
|
|
69
|
+
# won't fetch — they must be on disk (this dir's node_modules or an ancestor's).
|
|
70
|
+
# Reads the connector's own package.json, so this stays connector-agnostic. A
|
|
71
|
+
# bare `[ -d node_modules ]` is the wrong test: under pnpm/monorepo layouts the
|
|
72
|
+
# deps can live in an ancestor (or be hoisted), and a local node_modules can
|
|
73
|
+
# exist without the package being present. We check each dep's package.json
|
|
74
|
+
# exists in one of Node's resolution paths rather than `require.resolve(name)`,
|
|
75
|
+
# because resolving the package ENTRY can fail for ESM-only / `exports`-map
|
|
76
|
+
# packages even when they're fully installed and importable.
|
|
77
|
+
node_resolves() {
|
|
78
|
+
( CDPATH= cd -- "$SCRIPT_DIR" && node -e 'const fs=require("fs"),path=require("path");const d=require("./package.json").dependencies||{};for(const k of Object.keys(d)){const ps=require.resolve.paths(k)||[];if(!ps.some(b=>fs.existsSync(path.join(b,k,"package.json"))))process.exit(1);}' ) >/dev/null 2>&1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Can we actually WRITE into this directory right now? Any dep install must land
|
|
82
|
+
# `node_modules/` here, so if the harness mounts the connector read-only (common
|
|
83
|
+
# when skills live under ~/.<agent>/skills, outside the agent's writable
|
|
84
|
+
# workspace) no install can succeed in place — the only fixes are to run it
|
|
85
|
+
# unsandboxed or grant write access. Two deliberate choices:
|
|
86
|
+
# - Probe with a real create+remove, not `[ -w ]`: a sandbox denies the write
|
|
87
|
+
# at the syscall while the permission bits still look writable.
|
|
88
|
+
# - Probe by creating a DIRECTORY (`mkdir`), not a file: that's the install's
|
|
89
|
+
# very first on-disk action (node_modules/ and the cache dirs), and at least
|
|
90
|
+
# one sandbox (Claude Code) permits creating a file here while denying
|
|
91
|
+
# `mkdir` — a file-based probe reports writable and the install then EPERMs.
|
|
92
|
+
dir_writable() {
|
|
93
|
+
_t="$SCRIPT_DIR/.preflight-write-test.$$"
|
|
94
|
+
mkdir "$_t" 2>/dev/null || return 1
|
|
95
|
+
rmdir "$_t" 2>/dev/null
|
|
96
|
+
return 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ---- 1) Pick a runtime -----------------------------------------------------
|
|
100
|
+
# Node 22.18+ (native TS strip) is the baseline and the preferred runner whenever
|
|
101
|
+
# it's present — it runs the .ts scripts directly. Bun is the fallback runner
|
|
102
|
+
# only when there's no usable Node. We DON'T lean on Bun's implicit auto-install:
|
|
103
|
+
# it's silently suppressed by any ancestor node_modules (monorepo layouts) and
|
|
104
|
+
# fails the same way Node's `npm install` does under a sandbox that blocks Bun's
|
|
105
|
+
# home cache — so missing deps are always an explicit, cache-local install step.
|
|
106
|
+
nresolves=false
|
|
107
|
+
node_ge_2218 && node_resolves && nresolves=true
|
|
108
|
+
|
|
109
|
+
if [ "$nresolves" = true ]; then
|
|
110
|
+
runner=node
|
|
111
|
+
ready=true
|
|
112
|
+
elif node_ge_2218; then
|
|
113
|
+
runner=node # present but deps unresolved — install step below
|
|
114
|
+
ready=false
|
|
115
|
+
elif has bun; then
|
|
116
|
+
runner=bun # no usable Node — Bun runs the scripts; install step below
|
|
117
|
+
ready=false
|
|
118
|
+
else
|
|
119
|
+
echo "PREFLIGHT_STATUS: NEEDS_ACTION"
|
|
120
|
+
echo "PREFLIGHT_RECOMMENDATION: no Node 22.18+ or Bun found — install Node 22.18+ (ships npm) or Bun, then run \`<runner> ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
|
|
121
|
+
exit "$EXIT_NEEDS_ACTION"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# ---- 2) Install deps if the chosen runtime can't run as-is -----------------
|
|
125
|
+
# Two distinct sandbox failures to disambiguate, because their fixes differ:
|
|
126
|
+
# a) This directory is READ-ONLY (skills installed outside the agent's
|
|
127
|
+
# writable workspace). No install can place node_modules here — the agent
|
|
128
|
+
# must run it unsandboxed or be granted write access. A cache flag can't
|
|
129
|
+
# help.
|
|
130
|
+
# b) The directory is writable but the HOME dir is sandboxed, so the default
|
|
131
|
+
# ~/.npm / ~/.bun cache write is what EPERMs mid-install. Pointing the cache
|
|
132
|
+
# INSIDE this directory sidesteps that without disabling the sandbox (and is
|
|
133
|
+
# harmless otherwise — just an unused cache dir).
|
|
134
|
+
# So probe writability first and recommend accordingly.
|
|
135
|
+
if [ "$ready" != true ]; then
|
|
136
|
+
echo "PREFLIGHT_STATUS: NEEDS_ACTION"
|
|
137
|
+
[ "$runner" = bun ] && install_cmd="bun install" || install_cmd="npm install"
|
|
138
|
+
if ! dir_writable; then
|
|
139
|
+
echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed and ${SCRIPT_DIR} is read-only in the current sandbox (a test write there failed) — \`${install_cmd}\` can't place node_modules here. Run the install with the sandbox disabled, or grant the agent write access to ${SCRIPT_DIR} (a cache flag won't help — node_modules must land in this directory); then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
|
|
140
|
+
elif [ "$runner" = bun ]; then
|
|
141
|
+
echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed — run \`BUN_INSTALL_CACHE_DIR=\"${SCRIPT_DIR}/.bun-cache\" bun install\` in ${SCRIPT_DIR} (the workspace-local cache survives a sandbox that blocks ~/.bun; plain \`bun install\` works otherwise), then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
|
|
142
|
+
elif has npm; then
|
|
143
|
+
echo "PREFLIGHT_RECOMMENDATION: dependencies are not installed — run \`npm install --cache \"${SCRIPT_DIR}/.npm-cache\"\` in ${SCRIPT_DIR} (the workspace-local --cache survives a sandbox that blocks ~/.npm; plain \`npm install\` works otherwise), then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
|
|
144
|
+
else
|
|
145
|
+
# node >= 22.18 ships npm, so a missing npm means it was removed from the
|
|
146
|
+
# Node install. Restore it, then install with a workspace-local cache.
|
|
147
|
+
echo "PREFLIGHT_RECOMMENDATION: npm is missing (it ships with Node 22.18+) — reinstall/repair Node 22.18+, run \`npm install --cache \"${SCRIPT_DIR}/.npm-cache\"\` in ${SCRIPT_DIR}, then run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials."
|
|
148
|
+
fi
|
|
149
|
+
exit "$EXIT_NEEDS_ACTION"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# ---- 3) Ready --------------------------------------------------------------
|
|
153
|
+
# Runtime + deps are in place — the scripts run.
|
|
154
|
+
echo "PREFLIGHT_STATUS: READY"
|
|
155
|
+
echo "PREFLIGHT_RUNNER: ${runner}"
|
|
156
|
+
echo "PREFLIGHT_RECOMMENDATION: run \`${runner} ${SCRIPT_DIR}/scripts/<name>.ts --help\` to discover arguments and credentials, then run the script with the required env vars set."
|
|
157
|
+
exit "$EXIT_READY"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Microsoft Outlook (Microsoft Graph) — API gotchas
|
|
2
|
+
|
|
3
|
+
Behavioral notes for the Outlook tools, which call the **Microsoft Graph v1.0** REST API
|
|
4
|
+
(`https://graph.microsoft.com/v1.0`). Every claim here is sourced from the public Microsoft
|
|
5
|
+
Graph documentation; links are inline. Load this when a call returns an error you don't
|
|
6
|
+
understand, when ids stop resolving, or when paging / search / date-time handling behaves
|
|
7
|
+
unexpectedly.
|
|
8
|
+
|
|
9
|
+
## Authentication & identity
|
|
10
|
+
|
|
11
|
+
- Every request is authorized with a single OAuth 2.0 **bearer token** (`Authorization: Bearer
|
|
12
|
+
<token>`); there is no bot/user split. Use **getMe** as the auth probe — a `200` confirms the
|
|
13
|
+
connection works and returns your `id`, `displayName`, `mail`, and `userPrincipalName`.
|
|
14
|
+
- Resolve **"my email"** with getMe before composing or filtering, since the signed-in user's
|
|
15
|
+
address isn't otherwise known to the tools.
|
|
16
|
+
|
|
17
|
+
## Item ids change when items move — immutable ids are requested for you
|
|
18
|
+
|
|
19
|
+
By default a message/event/contact **id changes when the item is moved** from one container to
|
|
20
|
+
another: _"By default, this value changes when the item is moved from one container (such as a
|
|
21
|
+
folder or calendar) to another."_ ([message resource](https://learn.microsoft.com/en-us/graph/api/resources/message),
|
|
22
|
+
[event resource](https://learn.microsoft.com/en-us/graph/api/resources/event)) This is the dominant
|
|
23
|
+
cause of stale-id `404`s.
|
|
24
|
+
|
|
25
|
+
This connector sends `Prefer: IdType="ImmutableId"` on **every** request, so the ids you get back
|
|
26
|
+
are stable: _"Immutable identifiers (IDs) enable your application to obtain an ID that doesn't
|
|
27
|
+
change for the lifetime of the item"_ and _"will NOT change if the item is moved to a different
|
|
28
|
+
folder in the mailbox."_ ([immutable ids](https://learn.microsoft.com/en-us/graph/outlook-immutable-id))
|
|
29
|
+
The immutable id still changes if the item is moved to an **archive mailbox** or exported and
|
|
30
|
+
re-imported (same source).
|
|
31
|
+
|
|
32
|
+
- **moveMessage** returns the message in its new folder; use the id from that response for any
|
|
33
|
+
follow-up call. ([move message](https://learn.microsoft.com/en-us/graph/api/resources/message) — _"Move
|
|
34
|
+
the message to a folder. This creates a new copy of the message in the destination folder."_)
|
|
35
|
+
- Ids are **case-sensitive**: _"all identifiers in Microsoft Graph, are case-sensitive."_
|
|
36
|
+
([immutable ids](https://learn.microsoft.com/en-us/graph/outlook-immutable-id))
|
|
37
|
+
- A sent message can be located in **Sent Items** by creating the draft with the immutable-id
|
|
38
|
+
header and reusing that id after sending. ([immutable ids](https://learn.microsoft.com/en-us/graph/outlook-immutable-id))
|
|
39
|
+
|
|
40
|
+
## Shared mailboxes
|
|
41
|
+
|
|
42
|
+
Pass a `mailbox` (UPN/email) to act on another user's mailbox via `/users/{id|userPrincipalName}`.
|
|
43
|
+
This only works when that user has **shared the folder or delegated access** to the signed-in
|
|
44
|
+
user: _"If Garth hasn't shared his Inbox with John, nor has he delegated his mailbox to John,
|
|
45
|
+
specifying Garth's user ID or user principal name in those GET operations return an error."_
|
|
46
|
+
([shared/delegated folders](https://learn.microsoft.com/en-us/graph/outlook-share-messages-folders)).
|
|
47
|
+
Reading/writing a shared folder uses the `Mail.Read.Shared` / `Mail.ReadWrite.Shared` delegated
|
|
48
|
+
permissions (same source). Sending **from** a shared mailbox relies on Exchange-side delegation —
|
|
49
|
+
the message `sender`/`from` _"can be set to a different value when sending a message from a shared
|
|
50
|
+
mailbox … or as a delegate."_ ([message resource](https://learn.microsoft.com/en-us/graph/api/resources/message))
|
|
51
|
+
|
|
52
|
+
## Paging — `next_cursor` is an opaque URL
|
|
53
|
+
|
|
54
|
+
List tools return `next_cursor`, which is Graph's `@odata.nextLink`. Pass it back verbatim as
|
|
55
|
+
`cursor`; don't parse or rebuild it: _"Use the entire URL in the `@odata.nextLink` property in a
|
|
56
|
+
GET request to retrieve the next page of results … Don't try to extract the `$skiptoken` or
|
|
57
|
+
`$skip` value and use it in a different request."_ ([paging](https://learn.microsoft.com/en-us/graph/paging))
|
|
58
|
+
|
|
59
|
+
- **listMessages** default page size is **10**, max **1000**: _"The default page size is 10
|
|
60
|
+
messages. Use `$top` to customize the page size, within the range of 1 and 1000."_
|
|
61
|
+
([list messages](https://learn.microsoft.com/en-us/graph/api/user-list-messages))
|
|
62
|
+
- **listCategories** is **not paged** — `masterCategories` returns the full set in one response.
|
|
63
|
+
([list masterCategories](https://learn.microsoft.com/en-us/graph/api/outlookuser-list-mastercategories))
|
|
64
|
+
|
|
65
|
+
## Search vs. filter (listMessages, listContacts, listEvents)
|
|
66
|
+
|
|
67
|
+
- **`search`** is a full-text KQL query over messages. With no property prefix it searches the
|
|
68
|
+
default properties **from, subject, body**; you can also target a property (`subject:invoice`,
|
|
69
|
+
`from:acme`). Message search results are _"sorted by the date and time the message was sent"_ and
|
|
70
|
+
_"A `$search` request returns up to 1,000 results."_
|
|
71
|
+
([$search](https://learn.microsoft.com/en-us/graph/search-query-parameter)) Because the order is
|
|
72
|
+
fixed to sent date/time, don't expect to control ordering while searching.
|
|
73
|
+
- **`filter`** is an OData `$filter` for exact matches (`isRead eq false`, `importance eq 'high'`).
|
|
74
|
+
When `$filter` and `$orderby` are combined on messages, _"Properties that appear in `$orderby`
|
|
75
|
+
must also appear in `$filter`"_ and in the same order.
|
|
76
|
+
([list messages](https://learn.microsoft.com/en-us/graph/api/user-list-messages))
|
|
77
|
+
- **listContacts** filtering is **limited to email-address match**: _"You can use `$filter`, `any`,
|
|
78
|
+
and the `eq` operator on only the **address** sub-property of instances in an **emailAddresses**
|
|
79
|
+
collection. That is, you can't filter on the **name** or any other sub-property … nor can you
|
|
80
|
+
apply any other operator or function with `filter`, such as `ne`, `le`, and `startswith()`."_
|
|
81
|
+
([list contacts](https://learn.microsoft.com/en-us/graph/api/user-list-contacts)) For name lookups,
|
|
82
|
+
list and match the returned results client-side.
|
|
83
|
+
|
|
84
|
+
## Messages
|
|
85
|
+
|
|
86
|
+
- **sendMail returns `202 Accepted` with no body and no message id.** _"If successful, this method
|
|
87
|
+
returns `202 Accepted` response code. It doesn't return anything in the response body"_ and
|
|
88
|
+
_"A `202 Accepted` response code … doesn't indicate that the request processing has completed.
|
|
89
|
+
Delivery of the message is subject to Exchange Online limitations and throttling."_
|
|
90
|
+
([sendMail](https://learn.microsoft.com/en-us/graph/api/user-sendmail)) When you need the sent
|
|
91
|
+
message's id, use **createDraft** (returns the draft id) then **sendDraft**.
|
|
92
|
+
- **saveToSentItems** defaults to true: _"Specify it only if the parameter is false; default is
|
|
93
|
+
true."_ (same source) Reply, reply-all, and forward also save to Sent Items.
|
|
94
|
+
([message resource](https://learn.microsoft.com/en-us/graph/api/resources/message))
|
|
95
|
+
- **createReplyDraft / forward draft** create a draft pre-populated with the original recipients
|
|
96
|
+
and quoted body that you can edit and send later: _"Create a draft to reply to the sender of a
|
|
97
|
+
message … You can update the draft later … Send the draft message in a subsequent operation."_
|
|
98
|
+
([createReply](https://learn.microsoft.com/en-us/graph/api/message-createreply))
|
|
99
|
+
- **deleteMessage is a soft delete.** The message moves to the **Deleted Items** folder —
|
|
100
|
+
_"deleteditems: The folder items are moved to when they're deleted"_ — and `deleteMessage`
|
|
101
|
+
returns `204 No Content`. A separate _"Permanently delete"_ operation purges items; this
|
|
102
|
+
connector does not expose it. ([mailFolder](https://learn.microsoft.com/en-us/graph/api/resources/mailfolder),
|
|
103
|
+
[delete message](https://learn.microsoft.com/en-us/graph/api/message-delete))
|
|
104
|
+
- **updateMessage** is a PATCH: only the fields you send change. **categories REPLACE** the existing
|
|
105
|
+
set (read current via getMessage and include them to append). Subject/body are editable only on
|
|
106
|
+
drafts.
|
|
107
|
+
- **bodyPreview** is _"The first 255 characters of the message body. It is in text format."_ — use
|
|
108
|
+
getMessage for the full body. ([message resource](https://learn.microsoft.com/en-us/graph/api/resources/message))
|
|
109
|
+
- **getMessage** body format is controlled by `Prefer: outlook.body-content-type` (text or html);
|
|
110
|
+
this connector defaults to text. ([list messages](https://learn.microsoft.com/en-us/graph/api/user-list-messages))
|
|
111
|
+
|
|
112
|
+
### Well-known folder names
|
|
113
|
+
|
|
114
|
+
`folderId` / `destinationId` / `parentFolderId` accept either a folder id or a well-known name —
|
|
115
|
+
_"Instead of using the corresponding folder id value, for convenience, you can use the well-known
|
|
116
|
+
folder names."_ Common ones: `inbox`, `drafts`, `sentitems`, `deleteditems`, `archive`, `junkemail`.
|
|
117
|
+
([mailFolder](https://learn.microsoft.com/en-us/graph/api/resources/mailfolder))
|
|
118
|
+
|
|
119
|
+
## Attachments
|
|
120
|
+
|
|
121
|
+
- `hasAttachments` is **false when a message has only inline attachments**: _"This property doesn't
|
|
122
|
+
include inline attachments, so if a message contains only inline attachments, this property is
|
|
123
|
+
false."_ Still call **listAttachments** to be sure. ([message resource](https://learn.microsoft.com/en-us/graph/api/resources/message))
|
|
124
|
+
- An attachment is one of three kinds — a **file** (`fileAttachment`), an **item** (`itemAttachment`),
|
|
125
|
+
or a **reference** (`referenceAttachment`). ([attachment](https://learn.microsoft.com/en-us/graph/api/resources/attachment))
|
|
126
|
+
- **getAttachment** returns `contentBytes` (_"The base64-encoded contents of the file"_) only for
|
|
127
|
+
**file** attachments — item and reference attachments carry no inline bytes.
|
|
128
|
+
([fileAttachment](https://learn.microsoft.com/en-us/graph/api/resources/fileattachment))
|
|
129
|
+
- **Outgoing attachments must be under 3 MB** and are sent inline (base64) in the same request:
|
|
130
|
+
_"If the file size is under 3 MB, do a single POST on the attachments navigation property … If
|
|
131
|
+
the file size is between 3 MB and 150 MB, create an upload session."_
|
|
132
|
+
([large attachments](https://learn.microsoft.com/en-us/graph/outlook-large-attachments)) This
|
|
133
|
+
connector supports only the under-3-MB inline path; larger files (which require an upload session)
|
|
134
|
+
are not supported and surface as a `413`.
|
|
135
|
+
|
|
136
|
+
## Calendar
|
|
137
|
+
|
|
138
|
+
- **listEvents** returns single events **plus recurring series masters** (the recurrence
|
|
139
|
+
definition), not expanded occurrences: _"The list contains single instance meetings and series
|
|
140
|
+
masters."_ ([event resource](https://learn.microsoft.com/en-us/graph/api/resources/event))
|
|
141
|
+
- **listCalendarView** expands recurring series into individual occurrences over a time window:
|
|
142
|
+
_"Get the occurrences, exceptions and single instances of events in a calendar view defined by a
|
|
143
|
+
time range."_ Its `startDateTime`/`endDateTime` are interpreted by their offset, **defaulting to
|
|
144
|
+
UTC** when none is given: _"If no timezone offset is included in the value, it is interpreted as
|
|
145
|
+
UTC."_ (`$top` range is 1–1000.) ([list calendarView](https://learn.microsoft.com/en-us/graph/api/calendar-list-calendarview))
|
|
146
|
+
- **Event start/end** are `{ dateTime, timeZone }`. `dateTime` is a **naive local timestamp** in
|
|
147
|
+
the form `{date}T{time}` (e.g. `2026-07-01T15:30:00`) with **no trailing Z or offset** — the zone
|
|
148
|
+
goes in the separate `timeZone` field. `timeZone` accepts a **Windows name** (e.g. _"Pacific
|
|
149
|
+
Standard Time"_) _"as well as the other time zones supported by the calendar API"_ (IANA names
|
|
150
|
+
such as `America/Los_Angeles` are listed). ([dateTimeTimeZone](https://learn.microsoft.com/en-us/graph/api/resources/datetimetimezone))
|
|
151
|
+
- **All-day events**: _"If true, regardless of whether it's a single-day or multi-day event, start,
|
|
152
|
+
and endtime must be set to midnight and be in the same time zone."_
|
|
153
|
+
([event resource](https://learn.microsoft.com/en-us/graph/api/resources/event)) In practice `end`
|
|
154
|
+
is midnight of the day **after** the last day (so a one-day all-day event ends on the next day's
|
|
155
|
+
midnight) — shown in Microsoft Q&A examples; the API reference states only the midnight + same-zone
|
|
156
|
+
requirement.
|
|
157
|
+
- **isOnlineMeeting**: set true to attach an online (Teams) meeting — _"After you set
|
|
158
|
+
isOnlineMeeting to true, Microsoft Graph initializes onlineMeeting"_ and the provider is
|
|
159
|
+
`teamsForBusiness`. ([event resource](https://learn.microsoft.com/en-us/graph/api/resources/event))
|
|
160
|
+
- **updateEvent attendees REPLACE** the existing list — read current via getEvent, append, then
|
|
161
|
+
update. (PATCH sends only the fields you set.)
|
|
162
|
+
- **deleteEvent** cancels the event; for meetings you organize, attendees are notified
|
|
163
|
+
(Graph exposes a _"Cancel event"_ that _"Send[s] a cancellation message from the organizer to all
|
|
164
|
+
the attendees"_). ([event resource](https://learn.microsoft.com/en-us/graph/api/resources/event))
|
|
165
|
+
- Output enum values are Graph's own: `showAs` ∈ `free, tentative, busy, oof, workingElsewhere,
|
|
166
|
+
unknown`; event `type` ∈ `singleInstance, occurrence, exception, seriesMaster`; `sensitivity` ∈
|
|
167
|
+
`normal, personal, private, confidential`. ([event resource](https://learn.microsoft.com/en-us/graph/api/resources/event))
|
|
168
|
+
Attendee `type` ∈ `required, optional, resource`; the attendee `status.response` is one of
|
|
169
|
+
`none, accepted, declined`, etc. ([attendee](https://learn.microsoft.com/en-us/graph/api/resources/attendee))
|
|
170
|
+
|
|
171
|
+
## Contacts
|
|
172
|
+
|
|
173
|
+
- A personal contact stores up to **three email addresses** — the resource exposes
|
|
174
|
+
`primaryEmailAddress`, `secondaryEmailAddress`, and `tertiaryEmailAddress`.
|
|
175
|
+
([contact resource](https://learn.microsoft.com/en-us/graph/api/resources/contact))
|
|
176
|
+
- **updateContact** array fields (`emailAddresses`, `businessPhones`) **replace** existing values —
|
|
177
|
+
read current via getContact and merge.
|
|
178
|
+
- Updating other properties may auto-regenerate `displayName`: _"later updates to other properties
|
|
179
|
+
may cause an automatically generated value to overwrite the displayName value you have specified.
|
|
180
|
+
To preserve a pre-existing value, always include it as displayName."_
|
|
181
|
+
([contact resource](https://learn.microsoft.com/en-us/graph/api/resources/contact))
|
|
182
|
+
|
|
183
|
+
## Categories
|
|
184
|
+
|
|
185
|
+
Categories are user-defined names in a **master list**; you apply one by assigning its
|
|
186
|
+
`displayName` to an item's `categories`: _"You can apply a category to an item by assigning the
|
|
187
|
+
displayName property of the category to the categories collection of the item."_ Each category's
|
|
188
|
+
**color** is a preset constant (`None`, `preset0`, `preset1`, … up to 25 colors) that comes from the
|
|
189
|
+
master-list entry. ([outlookCategory](https://learn.microsoft.com/en-us/graph/api/resources/outlookcategory))
|
|
190
|
+
Use **listCategories** to discover the valid names. Categories are per-user (no `mailbox` option).
|
|
191
|
+
|
|
192
|
+
## Errors, recovery & throttling
|
|
193
|
+
|
|
194
|
+
Graph returns a JSON error envelope: a single `error` object with `code` (machine-readable) and
|
|
195
|
+
`message`. _"You should only code against error codes returned in `code` properties."_
|
|
196
|
+
([errors](https://learn.microsoft.com/en-us/graph/errors)) Common statuses
|
|
197
|
+
([HTTP status codes](https://learn.microsoft.com/en-us/graph/errors)):
|
|
198
|
+
|
|
199
|
+
| Status | Meaning | Recovery |
|
|
200
|
+
| ------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
201
|
+
| `401` | _"Required authentication information is either missing or not valid."_ | Reconnect the Outlook account (token invalid/expired). |
|
|
202
|
+
| `403` | _"Access is denied … The user does not have enough permission or does not have a required license."_ | Reconnect to grant the permission; shared-mailbox access also needs Exchange-side delegation. |
|
|
203
|
+
| `404` | _"The requested resource doesn't exist."_ | The id may be stale — re-fetch it from the relevant list/get tool and retry. |
|
|
204
|
+
| `413` | _"The request size exceeds the maximum limit."_ | Attachment too large — keep inline attachments under 3 MB. |
|
|
205
|
+
| `429` | _"Client application has been throttled."_ | Back off, then retry. |
|
|
206
|
+
|
|
207
|
+
**Throttling (`429`)**: Graph _"Returns HTTP status code 429 Too Many Requests"_ and _"Returns a
|
|
208
|
+
suggested wait time in the response header of the failed request."_ Best practice: _"Wait the number
|
|
209
|
+
of seconds specified in the `Retry-After` header"_ and retry.
|
|
210
|
+
([throttling](https://learn.microsoft.com/en-us/graph/throttling))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineTool, handleIfScriptMain } from "@zapier/connectors-sdk";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { connectionResolvers } from "../connections.ts";
|
|
6
|
+
import {
|
|
7
|
+
GRAPH_BASE,
|
|
8
|
+
mailboxRoot,
|
|
9
|
+
outlookFetch,
|
|
10
|
+
parseGraphResponse,
|
|
11
|
+
} from "../lib/graph.ts";
|
|
12
|
+
|
|
13
|
+
const inputSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
messageId: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe(
|
|
18
|
+
"Message id from listMessages or another message tool. Opaque and case-sensitive; changes when the message is moved between folders.",
|
|
19
|
+
),
|
|
20
|
+
destinationId: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe(
|
|
23
|
+
"Target folder: a folder id from listMailFolders, or a well-known name (inbox, archive, deleteditems, junkemail, drafts, sentitems).",
|
|
24
|
+
),
|
|
25
|
+
mailbox: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe(
|
|
28
|
+
"Shared-mailbox address (UPN/email) to copy within instead of your own. Requires shared-mailbox delegation. Omit for your own mailbox.",
|
|
29
|
+
)
|
|
30
|
+
.optional(),
|
|
31
|
+
})
|
|
32
|
+
.strict();
|
|
33
|
+
|
|
34
|
+
const outputSchema = z.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
parentFolderId: z.string(),
|
|
37
|
+
subject: z.string(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const definition = defineTool({
|
|
41
|
+
name: "copyMessage",
|
|
42
|
+
title: "Copy Message",
|
|
43
|
+
description:
|
|
44
|
+
"Copy a message into another folder, leaving the original in place. Resolve the message id via listMessages first. Returns the new copy, which has its own id distinct from the original.",
|
|
45
|
+
inputSchema,
|
|
46
|
+
outputSchema,
|
|
47
|
+
annotations: {
|
|
48
|
+
readOnlyHint: false,
|
|
49
|
+
destructiveHint: false,
|
|
50
|
+
idempotentHint: false,
|
|
51
|
+
openWorldHint: true,
|
|
52
|
+
},
|
|
53
|
+
connection: "microsoft-outlook",
|
|
54
|
+
run: async (input, ctx) => {
|
|
55
|
+
const url = `${GRAPH_BASE}${mailboxRoot(input.mailbox)}/messages/${encodeURIComponent(
|
|
56
|
+
input.messageId,
|
|
57
|
+
)}/copy`;
|
|
58
|
+
const res = await outlookFetch(ctx.fetch, "copyMessage", url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: JSON.stringify({ destinationId: input.destinationId }),
|
|
61
|
+
});
|
|
62
|
+
return parseGraphResponse(res);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default definition;
|
|
67
|
+
|
|
68
|
+
await handleIfScriptMain(import.meta, definition, { connectionResolvers });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineTool, handleIfScriptMain } from "@zapier/connectors-sdk";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { connectionResolvers } from "../connections.ts";
|
|
6
|
+
import { GRAPH_BASE, outlookFetch, parseGraphResponse } from "../lib/graph.ts";
|
|
7
|
+
import { contactSchema, outgoingContactSchema } from "../lib/schemas.ts";
|
|
8
|
+
|
|
9
|
+
const inputSchema = z.object({ ...outgoingContactSchema.shape }).strict();
|
|
10
|
+
|
|
11
|
+
const outputSchema = contactSchema;
|
|
12
|
+
|
|
13
|
+
const definition = defineTool({
|
|
14
|
+
name: "createContact",
|
|
15
|
+
title: "Create Contact",
|
|
16
|
+
description:
|
|
17
|
+
"Create a personal contact. No single field is required, but set at least a name (givenName/surname/displayName) or an email address so the contact is identifiable. Microsoft allows a maximum of 3 email addresses per contact.",
|
|
18
|
+
inputSchema,
|
|
19
|
+
outputSchema,
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: false,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
idempotentHint: false,
|
|
24
|
+
openWorldHint: true,
|
|
25
|
+
},
|
|
26
|
+
connection: "microsoft-outlook",
|
|
27
|
+
run: async (input, ctx) => {
|
|
28
|
+
const url = `${GRAPH_BASE}/me/contacts`;
|
|
29
|
+
const res = await outlookFetch(ctx.fetch, "createContact", url, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: JSON.stringify(input),
|
|
32
|
+
});
|
|
33
|
+
return parseGraphResponse(res);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default definition;
|
|
38
|
+
|
|
39
|
+
await handleIfScriptMain(import.meta, definition, { connectionResolvers });
|