@oomol-lab/open-connector 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/assets/open-connector/catalog/apps/aisa.json +6394 -0
  2. package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
  3. package/assets/open-connector/catalog/apps/bilibili.json +2586 -0
  4. package/assets/open-connector/catalog/apps/bitwarden.json +1423 -0
  5. package/assets/open-connector/catalog/apps/cronfree.json +303 -0
  6. package/assets/open-connector/catalog/apps/cufinder.json +368 -0
  7. package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
  8. package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
  9. package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
  10. package/assets/open-connector/catalog/apps/framejet.json +273 -0
  11. package/assets/open-connector/catalog/apps/futunn.json +2967 -0
  12. package/assets/open-connector/catalog/apps/gmail.json +298 -0
  13. package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
  14. package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
  15. package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
  16. package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
  17. package/assets/open-connector/catalog/apps/googlechat.json +694 -7
  18. package/assets/open-connector/catalog/apps/googledocs.json +4 -1
  19. package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
  20. package/assets/open-connector/catalog/apps/granola.json +23 -0
  21. package/assets/open-connector/catalog/apps/investoday_mcp.json +22228 -197
  22. package/assets/open-connector/catalog/apps/ixspy.json +2274 -0
  23. package/assets/open-connector/catalog/apps/keepa.json +3534 -987
  24. package/assets/open-connector/catalog/apps/linear.json +17 -0
  25. package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
  26. package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
  27. package/assets/open-connector/catalog/apps/notion.json +446 -1
  28. package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
  29. package/assets/open-connector/catalog/apps/odoo.json +619 -0
  30. package/assets/open-connector/catalog/apps/osv.json +663 -0
  31. package/assets/open-connector/catalog/apps/outlook.json +30 -8
  32. package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
  33. package/assets/open-connector/catalog/apps/pdf_vector.json +329 -0
  34. package/assets/open-connector/catalog/apps/postiz.json +334 -0
  35. package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
  36. package/assets/open-connector/catalog/apps/pulsetic.json +406 -0
  37. package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
  38. package/assets/open-connector/catalog/apps/shopee.json +1785 -0
  39. package/assets/open-connector/catalog/apps/slack.json +304 -0
  40. package/assets/open-connector/catalog/apps/slackbot.json +295 -0
  41. package/assets/open-connector/catalog/apps/speak_ai.json +360 -0
  42. package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
  43. package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
  44. package/assets/open-connector/catalog/apps/youtube.json +4 -1
  45. package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
  46. package/assets/open-connector/catalog/apps/zyhub.json +1585 -0
  47. package/assets/open-connector/catalog/apps-index.json +1 -1
  48. package/package.json +1 -1
  49. package/src/core/cast.d.ts +10 -0
  50. package/src/core/cast.js +14 -0
  51. package/src/core/guarded-fetch.d.ts +6 -3
  52. package/src/core/guarded-fetch.js +6 -3
  53. package/src/core/json-schema.d.ts +5 -1
  54. package/src/core/json-schema.js +14 -2
  55. package/src/core/provider-scenarios.js +67 -13
  56. package/src/core/request.js +4 -1
  57. package/src/core/types.d.ts +10 -1
  58. package/src/mcp.js +10 -7
  59. package/src/oauth/oauth-client-config-service.d.ts +16 -1
  60. package/src/oauth/oauth-client-config-service.js +59 -3
  61. package/src/oauth/oauth-credential-refresh-service.js +3 -1
  62. package/src/oauth/oauth-flow-service.d.ts +6 -0
  63. package/src/oauth/oauth-flow-service.js +21 -4
  64. package/src/oauth/oauth-token.d.ts +13 -0
  65. package/src/providers/17track/runtime.js +1 -1
  66. package/src/providers/action-contracts.generated.d.ts +32 -8
  67. package/src/providers/aifinmarket/runtime.js +1 -1
  68. package/src/providers/aisa/executors.d.ts +4 -0
  69. package/src/providers/aisa/executors.js +864 -0
  70. package/src/providers/aliyun_ack/runtime.js +1 -1
  71. package/src/providers/aliyun_sls/runtime.js +1 -1
  72. package/src/providers/app_store_server/runtime-helpers.js +1 -1
  73. package/src/providers/baizhi_mcp/actions.d.ts +2 -0
  74. package/src/providers/baizhi_mcp/actions.js +40 -0
  75. package/src/providers/baizhi_mcp/executors.d.ts +3 -0
  76. package/src/providers/baizhi_mcp/executors.js +102 -0
  77. package/src/providers/bazhuayu/runtime.js +2 -2
  78. package/src/providers/bilibili/articles.d.ts +3 -0
  79. package/src/providers/bilibili/articles.js +369 -0
  80. package/src/providers/bilibili/executors.d.ts +8 -0
  81. package/src/providers/bilibili/executors.js +262 -0
  82. package/src/providers/bilibili/runtime.d.ts +95 -0
  83. package/src/providers/bilibili/runtime.js +236 -0
  84. package/src/providers/bilibili/upload.d.ts +14 -0
  85. package/src/providers/bilibili/upload.js +107 -0
  86. package/src/providers/bitwarden/executors.d.ts +3 -0
  87. package/src/providers/bitwarden/executors.js +146 -0
  88. package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
  89. package/src/providers/cloudflare_dns/executors.js +1 -1
  90. package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
  91. package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
  92. package/src/providers/cloudflare_dns/runtime.js +1 -1
  93. package/src/providers/cloudflare_r2/executors.js +1 -1
  94. package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
  95. package/src/providers/cloudflare_r2/runtime.js +1 -1
  96. package/src/providers/cloudflare_worker/executors.js +1 -1
  97. package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
  98. package/src/providers/cloudflare_worker/runtime.js +1 -1
  99. package/src/providers/cronfree/executors.d.ts +4 -0
  100. package/src/providers/cronfree/executors.js +89 -0
  101. package/src/providers/cufinder/executors.d.ts +4 -0
  102. package/src/providers/cufinder/executors.js +105 -0
  103. package/src/providers/dingtalk_mcp/runtime.js +1 -1
  104. package/src/providers/discordbot/executors.d.ts +0 -9
  105. package/src/providers/discordbot/executors.js +9 -101
  106. package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
  107. package/src/providers/discordbot/runtime-guilds.js +464 -0
  108. package/src/providers/discordbot/runtime.d.ts +42 -0
  109. package/src/providers/discordbot/runtime.js +124 -0
  110. package/src/providers/drawio_mcp/executors.d.ts +3 -0
  111. package/src/providers/drawio_mcp/executors.js +19 -0
  112. package/src/providers/drawio_mcp/runtime.d.ts +8 -0
  113. package/src/providers/drawio_mcp/runtime.js +175 -0
  114. package/src/providers/elevenreader/runtime.js +6 -5
  115. package/src/providers/fastmoss_mcp/executors.js +2 -2
  116. package/src/providers/finchina/executors.js +1 -1
  117. package/src/providers/framejet/executors.d.ts +4 -0
  118. package/src/providers/framejet/executors.js +19 -0
  119. package/src/providers/framejet/runtime.d.ts +16 -0
  120. package/src/providers/framejet/runtime.js +148 -0
  121. package/src/providers/futunn/actions.d.ts +2 -0
  122. package/src/providers/futunn/actions.js +152 -0
  123. package/src/providers/futunn/actions.research.d.ts +2 -0
  124. package/src/providers/futunn/actions.research.js +423 -0
  125. package/src/providers/futunn/actions.trading.d.ts +2 -0
  126. package/src/providers/futunn/actions.trading.js +204 -0
  127. package/src/providers/futunn/executors.d.ts +3 -0
  128. package/src/providers/futunn/executors.js +24 -0
  129. package/src/providers/futunn/runtime-research.d.ts +3 -0
  130. package/src/providers/futunn/runtime-research.js +133 -0
  131. package/src/providers/futunn/runtime-trading.d.ts +3 -0
  132. package/src/providers/futunn/runtime-trading.js +39 -0
  133. package/src/providers/futunn/runtime.d.ts +2 -0
  134. package/src/providers/futunn/runtime.js +50 -0
  135. package/src/providers/futunn/transport.d.ts +11 -0
  136. package/src/providers/futunn/transport.js +76 -0
  137. package/src/providers/gildata/executors.js +1 -1
  138. package/src/providers/gmail/attachment-stream.d.ts +2 -0
  139. package/src/providers/gmail/attachment-stream.js +162 -0
  140. package/src/providers/gmail/executors.d.ts +3 -1
  141. package/src/providers/gmail/executors.js +54 -26
  142. package/src/providers/gmail/message.d.ts +6 -8
  143. package/src/providers/gmail/message.js +4 -0
  144. package/src/providers/gmail/scopes.d.ts +32 -0
  145. package/src/providers/gmail/scopes.js +37 -0
  146. package/src/providers/google_analytics/executors.js +2 -2
  147. package/src/providers/google_analytics/scopes.js +6 -1
  148. package/src/providers/google_bigquery/executors.js +2 -2
  149. package/src/providers/google_bigquery/scopes.js +2 -0
  150. package/src/providers/google_search_console/executors.js +2 -2
  151. package/src/providers/google_search_console/scopes.js +2 -0
  152. package/src/providers/googleads/executors.js +1 -1
  153. package/src/providers/googleads/scopes.d.ts +1 -3
  154. package/src/providers/googleads/scopes.js +2 -4
  155. package/src/providers/googlecalendar/executors.js +1 -1
  156. package/src/providers/googlecalendar/runtime-shared.js +2 -2
  157. package/src/providers/googlecalendar/scopes.js +2 -0
  158. package/src/providers/googlechat/executors.d.ts +1 -3
  159. package/src/providers/googlechat/executors.js +219 -28
  160. package/src/providers/googlechat/members.d.ts +55 -0
  161. package/src/providers/googlechat/members.js +293 -0
  162. package/src/providers/googlechat/runtime.d.ts +16 -0
  163. package/src/providers/googlechat/runtime.js +25 -0
  164. package/src/providers/googlechat/scopes.d.ts +22 -3
  165. package/src/providers/googlechat/scopes.js +30 -7
  166. package/src/providers/googledocs/executors.js +1 -1
  167. package/src/providers/googledrive/executors.js +2 -2
  168. package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
  169. package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
  170. package/src/providers/googledrive/runtime-collaboration.js +1 -1
  171. package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
  172. package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
  173. package/src/providers/googleforms/executors.js +2 -2
  174. package/src/providers/googleforms/scopes.d.ts +0 -3
  175. package/src/providers/googleforms/scopes.js +2 -6
  176. package/src/providers/googlemeet/executors.js +2 -2
  177. package/src/providers/googlemeet/scopes.d.ts +0 -3
  178. package/src/providers/googlemeet/scopes.js +2 -6
  179. package/src/providers/googlesheets/executors.js +1 -1
  180. package/src/providers/googlesheets/scopes.js +2 -0
  181. package/src/providers/googleslides/executors.js +2 -2
  182. package/src/providers/googleslides/scopes.d.ts +0 -3
  183. package/src/providers/googleslides/scopes.js +2 -6
  184. package/src/providers/googletasks/executors.js +2 -2
  185. package/src/providers/googletasks/scopes.d.ts +0 -3
  186. package/src/providers/googletasks/scopes.js +2 -6
  187. package/src/providers/granola/actions.js +2 -0
  188. package/src/providers/granola/runtime-mcp.js +2 -0
  189. package/src/providers/granola/runtime.js +2 -2
  190. package/src/providers/indiegogo/executors.js +1 -1
  191. package/src/providers/investoday_mcp/executors.js +2 -2
  192. package/src/providers/investoday_mcp/official-actions.js +19 -372
  193. package/src/providers/investoday_mcp/official-tool-types.d.ts +10 -0
  194. package/src/providers/investoday_mcp/official-tool-types.js +4 -0
  195. package/src/providers/investoday_mcp/official-tools/charts.d.ts +2 -0
  196. package/src/providers/investoday_mcp/official-tools/charts.js +32 -0
  197. package/src/providers/investoday_mcp/official-tools/fixed-income.d.ts +2 -0
  198. package/src/providers/investoday_mcp/official-tools/fixed-income.js +326 -0
  199. package/src/providers/investoday_mcp/official-tools/funds.d.ts +2 -0
  200. package/src/providers/investoday_mcp/official-tools/funds.js +1085 -0
  201. package/src/providers/investoday_mcp/official-tools/hong-kong.d.ts +2 -0
  202. package/src/providers/investoday_mcp/official-tools/hong-kong.js +275 -0
  203. package/src/providers/investoday_mcp/official-tools/indices-industries.d.ts +2 -0
  204. package/src/providers/investoday_mcp/official-tools/indices-industries.js +642 -0
  205. package/src/providers/investoday_mcp/official-tools/macro-calendar.d.ts +2 -0
  206. package/src/providers/investoday_mcp/official-tools/macro-calendar.js +398 -0
  207. package/src/providers/investoday_mcp/official-tools/other.d.ts +2 -0
  208. package/src/providers/investoday_mcp/official-tools/other.js +131 -0
  209. package/src/providers/investoday_mcp/official-tools/research-news.d.ts +2 -0
  210. package/src/providers/investoday_mcp/official-tools/research-news.js +302 -0
  211. package/src/providers/investoday_mcp/official-tools/stocks.d.ts +2 -0
  212. package/src/providers/investoday_mcp/official-tools/stocks.js +2829 -0
  213. package/src/providers/ixspy/executors.d.ts +3 -0
  214. package/src/providers/ixspy/executors.js +220 -0
  215. package/src/providers/iyunbiao/runtime.js +2 -2
  216. package/src/providers/keepa/actions.d.ts +1 -1
  217. package/src/providers/keepa/actions.js +371 -149
  218. package/src/providers/keepa/deal-attributes.d.ts +1 -0
  219. package/src/providers/keepa/deal-attributes.js +26 -0
  220. package/src/providers/keepa/runtime.js +212 -18
  221. package/src/providers/kingdee/executors.js +1 -1
  222. package/src/providers/kingdee/runtime.js +2 -2
  223. package/src/providers/kuaidi100/runtime.js +1 -1
  224. package/src/providers/linear/executors.js +12 -9
  225. package/src/providers/linear_mcp/actions.d.ts +47 -0
  226. package/src/providers/linear_mcp/actions.js +102 -0
  227. package/src/providers/linear_mcp/endpoints.d.ts +3 -0
  228. package/src/providers/linear_mcp/endpoints.js +3 -0
  229. package/src/providers/linear_mcp/executors.d.ts +3 -0
  230. package/src/providers/linear_mcp/executors.js +9 -0
  231. package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
  232. package/src/providers/linear_mcp/runtime-mcp.js +238 -0
  233. package/src/providers/lingxing_mcp/runtime.js +1 -1
  234. package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
  235. package/src/providers/metabase/mcp-runtime.js +1 -1
  236. package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
  237. package/src/providers/moneyforward_mcp/executors.js +11 -0
  238. package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
  239. package/src/providers/moneyforward_mcp/manifest.js +204 -0
  240. package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
  241. package/src/providers/moneyforward_mcp/runtime.js +296 -0
  242. package/src/providers/notion/executors.js +39 -3
  243. package/src/providers/notion_mcp/endpoints.d.ts +3 -0
  244. package/src/providers/notion_mcp/endpoints.js +3 -0
  245. package/src/providers/notion_mcp/executors.d.ts +3 -0
  246. package/src/providers/notion_mcp/executors.js +176 -0
  247. package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
  248. package/src/providers/notion_mcp/mcp-response.js +314 -0
  249. package/src/providers/odoo/executors.d.ts +3 -0
  250. package/src/providers/odoo/executors.js +32 -0
  251. package/src/providers/odoo/runtime.d.ts +16 -0
  252. package/src/providers/odoo/runtime.js +140 -0
  253. package/src/providers/osv/executors.d.ts +2 -0
  254. package/src/providers/osv/executors.js +11 -0
  255. package/src/providers/osv/runtime.d.ts +9 -0
  256. package/src/providers/osv/runtime.js +66 -0
  257. package/src/providers/outlook/executors.js +27 -6
  258. package/src/providers/outlook/microsoft-graph.js +17 -6
  259. package/src/providers/outlook_calendar/executors.js +24 -7
  260. package/src/providers/patsnap_mcp/executors.js +2 -2
  261. package/src/providers/pdf_vector/executors.d.ts +4 -0
  262. package/src/providers/pdf_vector/executors.js +108 -0
  263. package/src/providers/postiz/executors.d.ts +4 -0
  264. package/src/providers/postiz/executors.js +83 -0
  265. package/src/providers/provider-runtime.d.ts +18 -0
  266. package/src/providers/provider-runtime.js +60 -4
  267. package/src/providers/proworkflow/executors.d.ts +4 -0
  268. package/src/providers/proworkflow/executors.js +135 -0
  269. package/src/providers/pulsetic/executors.d.ts +4 -0
  270. package/src/providers/pulsetic/executors.js +126 -0
  271. package/src/providers/registry.generated.js +24 -0
  272. package/src/providers/scrape_creators/catalog.js +1 -1
  273. package/src/providers/scrape_creators/runtime.js +1 -1
  274. package/src/providers/sellersprite_mcp/executors.js +2 -2
  275. package/src/providers/sellersprite_mcp/official-actions.js +3 -2
  276. package/src/providers/sentry/runtime.js +6 -7
  277. package/src/providers/shopee/actions.d.ts +2 -0
  278. package/src/providers/shopee/actions.js +474 -0
  279. package/src/providers/shopee/executors.d.ts +3 -0
  280. package/src/providers/shopee/executors.js +85 -0
  281. package/src/providers/shopee/runtime.d.ts +79 -0
  282. package/src/providers/shopee/runtime.js +693 -0
  283. package/src/providers/slack/actions.js +69 -3
  284. package/src/providers/slack/runtime.js +116 -21
  285. package/src/providers/slickdeals/runtime.js +1 -1
  286. package/src/providers/sorftime_mcp/executors.js +2 -2
  287. package/src/providers/speak_ai/executors.d.ts +3 -0
  288. package/src/providers/speak_ai/executors.js +187 -0
  289. package/src/providers/tikhub/bounded-response.d.ts +1 -1
  290. package/src/providers/tikhub/bounded-response.js +8 -34
  291. package/src/providers/tikhub/endpoint-catalog.js +1 -1
  292. package/src/providers/tikhub/runtime.js +2 -2
  293. package/src/providers/tongdaxin/executors.js +79 -15
  294. package/src/providers/tushare/runtime.js +22 -9
  295. package/src/providers/uspto/executors.js +1 -1
  296. package/src/providers/waffo/executors.js +1 -1
  297. package/src/providers/waffo/runtime.js +1 -1
  298. package/src/providers/wecom_mcp/runtime.js +1 -1
  299. package/src/providers/wisburg/executors.d.ts +4 -0
  300. package/src/providers/wisburg/executors.js +13 -0
  301. package/src/providers/wisburg/runtime.d.ts +12 -0
  302. package/src/providers/wisburg/runtime.js +133 -0
  303. package/src/providers/xydc_mcp/executors.js +2 -2
  304. package/src/providers/youtube/executors.js +1 -1
  305. package/src/providers/youtube/runtime.js +1 -1
  306. package/src/providers/zerotier/executors.d.ts +9 -0
  307. package/src/providers/zerotier/executors.js +629 -0
  308. package/src/providers/zerotier/runtime.d.ts +54 -0
  309. package/src/providers/zerotier/runtime.js +207 -0
  310. package/src/providers/zyhub/executors.d.ts +3 -0
  311. package/src/providers/zyhub/executors.js +240 -0
  312. package/src/server/api/openapi.js +12 -2
  313. package/src/server/api/runtime-api.d.ts +8 -1
  314. package/src/server/api/runtime-api.js +19 -1
  315. package/src/server/connect-server.js +10 -2
  316. package/src/server/files/transit-files.d.ts +3 -2
  317. package/src/server/files/transit-files.js +31 -35
  318. package/src/providers/sentry/scopes.d.ts +0 -1
  319. package/src/providers/sentry/scopes.js +0 -7
  320. /package/src/providers/{cloudflare-current-user.d.ts → cloudflare_dns/runtime-user.d.ts} +0 -0
@@ -0,0 +1,296 @@
1
+ import { sha256Hex } from "../../core/aws-sigv4.js";
2
+ import { compactObject, looseArray, optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
3
+ import { callMcpTool, listMcpTools } from "../mcp-tools.js";
4
+ import { createProviderTimeout, ProviderRequestError, providerInputError, providerResponseError, } from "../provider-runtime.js";
5
+ import { canonicalOfficeName, currentOfficeToolName, moneyforwardMcpEndpoint, moneyforwardService, moneyforwardTools, officeCodePattern, schemaDigest, } from "./manifest.js";
6
+ const defaultOptions = { deadlineMs: 90_000, closeGraceMs: 5_000 };
7
+ const defaultWritesPerMinute = 30;
8
+ const rateWindowMs = 60_000;
9
+ const maxResponseBytes = 8 * 1024 * 1024;
10
+ const officeCodeRegExp = new RegExp(officeCodePattern, "u");
11
+ // A Money Forward API 4xx relayed by the MCP server. Enough to call a read invalid input, but not
12
+ // proof that a write did nothing: batch writes can fail part-way and retries can hide a success.
13
+ const apiClientErrorPattern = /^API request failed: client error\b/u;
14
+ // The only answers that prove the tool handler never ran: the arguments failed the tool's input
15
+ // schema, or the tool does not exist. Output validation runs after the handler, so it is excluded.
16
+ const notExecutedPattern = /^(?:MCP error -32602: )?Input validation error\b|^MCP error -32601\b/u;
17
+ export function createMoneyforwardMcpHandlers(timing = defaultOptions) {
18
+ const takeWriteSlot = createWriteLimiter(timing.writesPerMinute ?? defaultWritesPerMinute);
19
+ const toolHandlers = moneyforwardTools.map((entry) => [
20
+ entry.actionName,
21
+ entry.isWrite
22
+ ? async (input, context) => {
23
+ takeWriteSlot(context.apiKey);
24
+ return runWrite(entry, input, context, timing);
25
+ }
26
+ : (input, context) => runRead(entry, input, context, timing),
27
+ ]);
28
+ const listTools = (_input, context) => withDeadline(context.signal, timing, async (signal) => ({
29
+ tools: await listMcpTools(toolOptions(context, signal, timing), { includeAnnotations: true }),
30
+ }));
31
+ return Object.fromEntries([...toolHandlers, ["list_tools", listTools]]);
32
+ }
33
+ export const moneyforwardMcpActionHandlers = createMoneyforwardMcpHandlers();
34
+ /** Accept the key when the server exposes every captured tool; report schema drift without refusing. */
35
+ export async function validateMoneyforwardMcpCredential(apiKey, fetcher, signal, schemaDigests) {
36
+ const tools = await withDeadline(signal, defaultOptions, (deadlineSignal) => listMcpTools(toolOptions({ apiKey, fetcher }, deadlineSignal, defaultOptions)));
37
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
38
+ const missing = moneyforwardTools.filter((entry) => !byName.has(entry.toolName)).map((entry) => entry.toolName);
39
+ if (missing.length > 0) {
40
+ throw providerInputError(`Money Forward MCP does not expose these tools for this key: ${missing.join(", ")}`);
41
+ }
42
+ const schemaDrift = moneyforwardTools
43
+ .filter((entry) => schemaDigest(byName.get(entry.toolName)?.inputSchema) !== expectedDigest(entry, schemaDigests))
44
+ .map((entry) => entry.toolName);
45
+ const keyHash = sha256Hex(apiKey).slice(0, 16);
46
+ return {
47
+ profile: {
48
+ accountId: `${moneyforwardService}:${keyHash}`,
49
+ displayName: `Money Forward MCP · ${keyHash.slice(-6)}`,
50
+ },
51
+ grantedScopes: [],
52
+ metadata: { mcpEndpoint: moneyforwardMcpEndpoint, schemaDrift },
53
+ };
54
+ }
55
+ async function runRead(entry, input, context, timing) {
56
+ const args = toolArguments(entry, input);
57
+ return withDeadline(context.signal, timing, async (signal) => {
58
+ const result = await callToolWithoutWaitingForClose({
59
+ ...toolOptions(context, signal, timing),
60
+ toolName: entry.toolName,
61
+ arguments: args,
62
+ });
63
+ return readPayload(entry.toolName, result);
64
+ });
65
+ }
66
+ async function runWrite(entry, input, context, timing) {
67
+ const args = toolArguments(entry, input);
68
+ const expectedName = optionalString(input.expected_office_name);
69
+ if (!expectedName)
70
+ throw writeError(providerInputError("expected_office_name is required"), "definitely_not_sent");
71
+ // Flipped by the authorizeTool hook, the last step before callMcpTool sends tools/call.
72
+ const phase = { dispatched: false };
73
+ try {
74
+ return await withDeadline(context.signal, timing, async (signal) => {
75
+ const options = toolOptions(context, signal, timing);
76
+ await confirmOffice(String(args.office_code), expectedName, options);
77
+ const result = await callToolWithoutWaitingForClose({
78
+ ...options,
79
+ toolName: entry.toolName,
80
+ arguments: args,
81
+ authorizeTool(tool) {
82
+ // A late tools/list must not dispatch after the backstop has already reported definitely_not_sent.
83
+ if (signal.aborted) {
84
+ throw providerResponseError("The Money Forward write was cancelled before it was sent; nothing was written.");
85
+ }
86
+ assertSchemaUnchanged(entry, tool, expectedDigest(entry, timing.schemaDigests));
87
+ phase.dispatched = true;
88
+ },
89
+ });
90
+ return readPayload(entry.toolName, result);
91
+ });
92
+ }
93
+ catch (error) {
94
+ throw classifyWriteFailure(entry, args, phase.dispatched, error);
95
+ }
96
+ }
97
+ async function confirmOffice(officeCode, expectedName, options) {
98
+ const office = await callToolWithoutWaitingForClose({
99
+ ...options,
100
+ toolName: currentOfficeToolName,
101
+ arguments: { office_code: officeCode },
102
+ });
103
+ const payload = optionalRecord(readPayload(currentOfficeToolName, office));
104
+ const actualName = optionalString(payload?.name);
105
+ if (payload?.code !== officeCode || !actualName) {
106
+ throw providerInputError(`Money Forward did not confirm office ${officeCode}; nothing was written.`);
107
+ }
108
+ if (canonicalOfficeName(actualName) !== canonicalOfficeName(expectedName)) {
109
+ throw providerInputError(`Office ${officeCode} is "${actualName}" in Money Forward, not "${expectedName}"; nothing was written.`);
110
+ }
111
+ }
112
+ function expectedDigest(entry, overrides) {
113
+ return overrides?.[entry.toolName] ?? entry.schemaDigest;
114
+ }
115
+ function assertSchemaUnchanged(entry, tool, expected) {
116
+ if (!tool) {
117
+ throw providerResponseError(`Money Forward MCP no longer exposes ${entry.toolName}; nothing was written.`);
118
+ }
119
+ if (schemaDigest(tool.inputSchema) !== expected) {
120
+ throw providerResponseError(`Money Forward changed the ${entry.toolName} input schema; writes stay blocked until the change is reviewed and the provider is updated. Nothing was written.`);
121
+ }
122
+ }
123
+ function classifyWriteFailure(entry, args, dispatched, error) {
124
+ const cause = error instanceof ProviderRequestError ? error : providerResponseError("Money Forward request failed");
125
+ if (readWriteOutcome(cause) !== undefined)
126
+ return cause;
127
+ if (!dispatched)
128
+ return writeError(cause, "definitely_not_sent");
129
+ const submitted = summarizeSubmission(entry, args);
130
+ if (optionalRecord(cause.details)?.notExecuted === true) {
131
+ // The pattern proves the MCP layer refused the call; a batch is still worth checking by hand.
132
+ const batchNote = Object.values(args).some(Array.isArray)
133
+ ? " This was a batch write: confirm that no item was applied before resubmitting."
134
+ : "";
135
+ const rejected = new ProviderRequestError(cause.status, `${cause.message}${batchNote}`, cause.details, cause.code);
136
+ return writeError(rejected, "rejected", submitted);
137
+ }
138
+ return new ProviderRequestError(502, `OUTCOME UNKNOWN: ${entry.toolName} may have been applied. Check the ledger with get_journals / get_transactions for the submitted content before retrying. Cause: ${cause.message}`, { writeOutcome: "outcome_unknown", submitted }, "provider_error");
139
+ }
140
+ function writeError(error, outcome, submitted) {
141
+ const details = { ...optionalRecord(error.details), writeOutcome: outcome, submitted };
142
+ return new ProviderRequestError(error.status, error.message, details, error.code);
143
+ }
144
+ /** What was submitted, so an unknown outcome can be reconciled by hand. Never includes credentials. */
145
+ function summarizeSubmission(entry, args) {
146
+ const journal = optionalRecord(args.journal);
147
+ const branches = Array.isArray(journal?.branches) ? journal.branches : undefined;
148
+ const arrayCounts = Object.fromEntries(Object.entries(args)
149
+ .filter(([, value]) => Array.isArray(value))
150
+ .map(([key, value]) => [`${key}_count`, value.length]));
151
+ return compactObject({
152
+ tool: entry.toolName,
153
+ office_code: args.office_code,
154
+ id: args.id,
155
+ transaction_id: args.transaction_id,
156
+ transaction_date: journal?.transaction_date,
157
+ branch_count: branches?.length,
158
+ debit_total: branches ? sumDebits(branches) : undefined,
159
+ ...arrayCounts,
160
+ });
161
+ }
162
+ function sumDebits(branches) {
163
+ return branches.reduce((total, branch) => total + (optionalNumber(optionalRecord(optionalRecord(branch)?.debitor)?.value) ?? 0), 0);
164
+ }
165
+ /**
166
+ * Validate office_code and drop the guard-only field before it reaches Money Forward. The action
167
+ * schema already requires both, but executors can be invoked without schema validation (tests, direct
168
+ * loader callers), and these checks are what keep a write from going to an unnamed office.
169
+ */
170
+ function toolArguments(entry, input) {
171
+ const { expected_office_name: _guard, ...args } = input;
172
+ if (entry.requiresOfficeCode && !(typeof args.office_code === "string" && officeCodeRegExp.test(args.office_code))) {
173
+ throw entry.isWrite
174
+ ? writeError(providerInputError("office_code is required in the form XXXX-XXXX"), "definitely_not_sent")
175
+ : providerInputError("office_code is required in the form XXXX-XXXX");
176
+ }
177
+ return args;
178
+ }
179
+ /**
180
+ * Rolling one-minute write budget per key (hashed, so the map never holds the key itself). Best-effort:
181
+ * it counts within one process, so separate processes or isolates each keep their own budget.
182
+ */
183
+ function createWriteLimiter(writesPerMinute) {
184
+ const recent = new Map();
185
+ return (apiKey) => {
186
+ const now = Date.now();
187
+ // Timestamps are appended in order, so a bucket is idle once its newest one has aged out.
188
+ for (const [key, times] of recent) {
189
+ if (now - (times.at(-1) ?? 0) >= rateWindowMs)
190
+ recent.delete(key);
191
+ }
192
+ const bucket = sha256Hex(apiKey);
193
+ const inWindow = (recent.get(bucket) ?? []).filter((at) => now - at < rateWindowMs);
194
+ if (inWindow.length >= writesPerMinute) {
195
+ throw new ProviderRequestError(429, `More than ${writesPerMinute} Money Forward writes in a minute; nothing was written. Wait and confirm with the user before continuing.`, { writeOutcome: "definitely_not_sent" }, "rate_limited");
196
+ }
197
+ recent.set(bucket, [...inWindow, now]);
198
+ };
199
+ }
200
+ // protocolVersion is deliberately left unset (legacy): in modern mode the SDK re-sends tools/call
201
+ // on a SEP-2243 header mismatch, which would be a silent write retry.
202
+ function toolOptions(context, signal, timing) {
203
+ return {
204
+ endpoint: moneyforwardMcpEndpoint,
205
+ service: "Money Forward",
206
+ fetcher: context.fetcher,
207
+ headers: { "mf-api-key": context.apiKey },
208
+ // Never follow a redirect: a cross-origin hop drops the key but would replay the ledger payload.
209
+ redirect: "manual",
210
+ signal,
211
+ terminateSession: true,
212
+ requestTimeoutMs: timing.deadlineMs,
213
+ maxResponseBytes,
214
+ toolListMaxBytes: 1024 * 1024,
215
+ toolListMaxPages: 5,
216
+ toolListMaxTools: 200,
217
+ };
218
+ }
219
+ /**
220
+ * Resolve as soon as the tool result arrives instead of after the SDK closes the session:
221
+ * the upstream cleanup awaits close() without a timeout, and a finished write must not be
222
+ * reported as unknown because a DELETE hung. Later cleanup errors are ignored.
223
+ */
224
+ function callToolWithoutWaitingForClose(input) {
225
+ return new Promise((resolve, reject) => {
226
+ callMcpTool({
227
+ ...input,
228
+ transformToolResult(result) {
229
+ resolve(result);
230
+ return result;
231
+ },
232
+ }).then(() => undefined, (error) => reject(error));
233
+ });
234
+ }
235
+ function readPayload(toolName, result) {
236
+ const text = readText(result);
237
+ if ("isError" in result && result.isError) {
238
+ const notExecuted = notExecutedPattern.test(text);
239
+ const isClientError = notExecuted || apiClientErrorPattern.test(text);
240
+ throw new ProviderRequestError(isClientError ? 400 : 502, `Money Forward ${toolName} failed: ${text.slice(0, 500)}`, { notExecuted }, isClientError ? "invalid_input" : "provider_error");
241
+ }
242
+ if ("structuredContent" in result && result.structuredContent !== undefined)
243
+ return result.structuredContent;
244
+ try {
245
+ return JSON.parse(text);
246
+ }
247
+ catch {
248
+ return { text };
249
+ }
250
+ }
251
+ function readText(result) {
252
+ return looseArray(optionalRecord(result)?.content)
253
+ .map((item) => {
254
+ const content = optionalRecord(item);
255
+ return content?.type === "text" && typeof content.text === "string" ? content.text : "";
256
+ })
257
+ .join("")
258
+ .trim();
259
+ }
260
+ /**
261
+ * Run with one deadline shared by every step. The grace timer is a backstop for SDK cleanup
262
+ * that ignores the abort; the losing promise is caught so it cannot become an unhandled rejection.
263
+ * Every Money Forward call goes through here, so auth failures are normalized in this one place.
264
+ */
265
+ async function withDeadline(parentSignal, timing, run) {
266
+ if (parentSignal?.aborted) {
267
+ throw new ProviderRequestError(504, "The Money Forward request was cancelled before it was sent", undefined, "provider_error");
268
+ }
269
+ const deadline = createProviderTimeout(parentSignal, timing.deadlineMs);
270
+ let grace;
271
+ const work = run(deadline.signal);
272
+ work.catch(() => undefined);
273
+ const backstop = new Promise((_resolve, reject) => {
274
+ grace = setTimeout(() => reject(new ProviderRequestError(504, "The Money Forward request timed out", undefined, "provider_error")), timing.deadlineMs + timing.closeGraceMs);
275
+ });
276
+ try {
277
+ return await Promise.race([work, backstop]);
278
+ }
279
+ catch (error) {
280
+ throw withAuthorizationCode(error);
281
+ }
282
+ finally {
283
+ deadline.cleanup();
284
+ clearTimeout(grace);
285
+ }
286
+ }
287
+ // The shared MCP error mapper reports 401/403 as provider_error; surface them as auth failures.
288
+ function withAuthorizationCode(error) {
289
+ if (error instanceof ProviderRequestError && (error.status === 401 || error.status === 403)) {
290
+ return new ProviderRequestError(error.status, error.message, error.details, "authorization_failed");
291
+ }
292
+ return error;
293
+ }
294
+ function readWriteOutcome(error) {
295
+ return optionalRecord(error.details)?.writeOutcome;
296
+ }
@@ -1,5 +1,5 @@
1
1
  import { compactObject } from "../../core/cast.js";
2
- import { createProviderFetch, createProviderProxyUrl, defineProviderExecutors, normalizeProviderProxyHeaders, ProviderRequestError, providerUserAgent, readProviderProxyErrorMessage, readProviderProxyResponse, requireBearerCredential, toProviderProxyError, } from "../provider-runtime.js";
2
+ import { createProviderFetch, createProviderProxyUrl, defineProviderExecutors, normalizeProviderProxyHeaders, providerInputError, ProviderRequestError, providerUserAgent, readProviderProxyErrorMessage, readProviderProxyResponse, requireBearerCredential, toProviderProxyError, withRetryAfterSeconds, } from "../provider-runtime.js";
3
3
  const service = "notion";
4
4
  const notionApiBaseUrl = "https://api.notion.com/v1";
5
5
  const notionFetch = createProviderFetch({ skipDnsValidation: true });
@@ -83,6 +83,12 @@ export const notionActionHandlers = {
83
83
  list_data_source_templates(input, context) {
84
84
  return notionListDataSourceTemplates(input, context.accessToken, context.fetcher);
85
85
  },
86
+ list_comments(input, context) {
87
+ return notionListComments(input, context.accessToken, context.fetcher);
88
+ },
89
+ create_comment(input, context) {
90
+ return notionCreateComment(input, context.accessToken, context.fetcher);
91
+ },
86
92
  };
87
93
  export const executors = defineProviderExecutors({
88
94
  service,
@@ -121,7 +127,7 @@ export const proxy = async (input, context) => {
121
127
  const response = await notionFetch(url, init);
122
128
  if (!response.ok) {
123
129
  const text = await readProviderProxyErrorMessage(response, "");
124
- throw new ProviderRequestError(response.status, text || `Notion request failed with HTTP ${response.status}`);
130
+ throw new ProviderRequestError(response.status, text || `Notion request failed with HTTP ${response.status}`, withRetryAfterSeconds(response));
125
131
  }
126
132
  return { ok: true, response: await readProviderProxyResponse(response) };
127
133
  }
@@ -595,6 +601,36 @@ async function notionListDataSourceTemplates(input, accessToken, fetcher) {
595
601
  }, fetcher);
596
602
  return payload ?? {};
597
603
  }
604
+ async function notionListComments(input, accessToken, fetcher) {
605
+ const payload = await notionRequest(accessToken, {
606
+ path: "/comments",
607
+ query: compactQuery({
608
+ block_id: String(input.blockId),
609
+ page_size: asNumber(input.pageSize),
610
+ start_cursor: asNonEmptyString(input.startCursor),
611
+ }),
612
+ }, fetcher);
613
+ return payload ?? {};
614
+ }
615
+ async function notionCreateComment(input, accessToken, fetcher) {
616
+ const parent = asObject(input.parent);
617
+ const discussionId = asNonEmptyString(input.discussion_id);
618
+ if (!parent === !discussionId) {
619
+ throw providerInputError("exactly one of parent or discussion_id is required");
620
+ }
621
+ const payload = await notionRequest(accessToken, {
622
+ method: "POST",
623
+ path: "/comments",
624
+ body: compactObject({
625
+ parent,
626
+ discussion_id: discussionId,
627
+ rich_text: Array.isArray(input.rich_text) ? input.rich_text : [],
628
+ attachments: Array.isArray(input.attachments) ? input.attachments : undefined,
629
+ display_name: asObject(input.display_name),
630
+ }),
631
+ }, fetcher);
632
+ return payload ?? {};
633
+ }
598
634
  async function notionRequest(accessToken, input, fetcher) {
599
635
  const url = new URL(`https://api.notion.com/v1${input.path}`);
600
636
  for (const [key, value] of Object.entries(input.query ?? {})) {
@@ -653,7 +689,7 @@ async function assertNotionResponse(response) {
653
689
  throw new ProviderRequestError(403, message);
654
690
  }
655
691
  if (response.status === 429) {
656
- throw new ProviderRequestError(429, message);
692
+ throw new ProviderRequestError(429, message, withRetryAfterSeconds(response));
657
693
  }
658
694
  throw new ProviderRequestError(response.status, message);
659
695
  }
@@ -0,0 +1,3 @@
1
+ /** Notion's official remote MCP server, which its own OAuth authorization server protects (RFC 9728). */
2
+ export declare const notionMcpEndpoint = "https://mcp.notion.com/mcp";
3
+ export declare const notionMcpIssuer = "https://mcp.notion.com";
@@ -0,0 +1,3 @@
1
+ /** Notion's official remote MCP server, which its own OAuth authorization server protects (RFC 9728). */
2
+ export const notionMcpEndpoint = "https://mcp.notion.com/mcp";
3
+ export const notionMcpIssuer = "https://mcp.notion.com";
@@ -0,0 +1,3 @@
1
+ import type { CredentialValidators, ProviderExecutors } from "../../core/types.ts";
2
+ export declare const executors: ProviderExecutors;
3
+ export declare const credentialValidators: CredentialValidators;
@@ -0,0 +1,176 @@
1
+ import { ProtocolError, SdkHttpError, UnauthorizedError } from "@modelcontextprotocol/client";
2
+ import { compactObject, looseArray, optionalBoolean, optionalInteger, optionalNumber, optionalRawString, optionalRecord, optionalString, optionalStringArray, requiredRawString, } from "../../core/cast.js";
3
+ import { withMcpClient } from "../mcp-client.js";
4
+ import { defineOAuthProviderExecutors, mapProviderActionSources, providerResponseError, providerUserAgent, ProviderRequestError, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
5
+ import { notionMcpEndpoint } from "./endpoints.js";
6
+ import { decodeNotionToolText, parseNotionComments, parseNotionPage, parseNotionSearch, parseNotionSelf, parseNotionToolAccess, } from "./mcp-response.js";
7
+ const service = "notion_mcp";
8
+ const selfUserId = "self";
9
+ const toolsByAction = {
10
+ get_self: {
11
+ tool: "notion-get-users",
12
+ arguments: () => ({ user_id: selfUserId }),
13
+ parse: (value) => compactObject({ user: parseNotionSelf(value) }),
14
+ },
15
+ search: {
16
+ tool: "notion-search",
17
+ arguments: searchArguments,
18
+ parse: (value) => ({ ...parseNotionSearch(value) }),
19
+ },
20
+ fetch_page: {
21
+ tool: "notion-fetch",
22
+ arguments: (input) => ({ id: requiredInputString(input.id, "id") }),
23
+ parse: (value, input) => ({ id: requiredInputString(input.id, "id"), ...parseNotionPage(value) }),
24
+ },
25
+ list_comments: {
26
+ tool: "notion-get-comments",
27
+ arguments: (input) => ({
28
+ page_id: requiredInputString(input.page_id, "page_id"),
29
+ include_all_blocks: optionalBoolean(input.include_all_blocks) ?? true,
30
+ include_resolved: optionalBoolean(input.include_resolved) ?? true,
31
+ }),
32
+ parse: (value) => ({ comments: parseNotionComments(value) }),
33
+ },
34
+ tool_access: {
35
+ tool: "notion-get-tool-access",
36
+ arguments: () => ({}),
37
+ parse: (value) => ({ tools: parseNotionToolAccess(value) }),
38
+ },
39
+ };
40
+ const handlers = mapProviderActionSources(service, toolsByAction, (_actionName, source) => async (input, context) => {
41
+ const result = await callNotionTool(context, source.tool, source.arguments(input));
42
+ return { ...source.parse(result.value, input), raw: result.raw };
43
+ });
44
+ export const executors = defineOAuthProviderExecutors(service, handlers, {
45
+ skipDnsValidation: true,
46
+ });
47
+ export const credentialValidators = {
48
+ /** Require every tool the actions map, then identify the user so the account key is the Notion user ID. */
49
+ async oauth2(input, { fetcher, signal }) {
50
+ const context = { accessToken: input.accessToken, fetcher, signal };
51
+ const { toolCount, self } = await withNotionMcpClient(context, "validate", async (client, signal) => {
52
+ const tools = await listNotionTools(client, signal);
53
+ const available = new Set(tools.map((tool) => tool.name));
54
+ const missing = Object.values(toolsByAction)
55
+ .map((source) => source.tool)
56
+ .filter((tool) => !available.has(tool));
57
+ if (missing.length > 0) {
58
+ throw new ProviderRequestError(502, `Notion MCP did not expose the tools this connector needs: ${[...new Set(missing)].join(", ")}.`);
59
+ }
60
+ const result = await client.callTool({ name: toolsByAction.get_self.tool, arguments: toolsByAction.get_self.arguments({}) }, { signal });
61
+ return {
62
+ toolCount: tools.length,
63
+ self: parseNotionSelf(readNotionToolResult(toolsByAction.get_self.tool, result).value),
64
+ };
65
+ });
66
+ const accountId = self?.id;
67
+ if (!accountId)
68
+ throw providerResponseError("Notion MCP did not identify the connected user.");
69
+ return {
70
+ profile: { accountId, displayName: self.name ?? self.email ?? accountId },
71
+ grantedScopes: optionalString(input.metadata.scope)?.split(" ") ?? [],
72
+ metadata: { mcpEndpoint: notionMcpEndpoint, discoveredToolCount: toolCount },
73
+ };
74
+ },
75
+ };
76
+ /** The recorded notion-search arguments: typed filters nest under `filters`; extra ones pass through beneath them. */
77
+ function searchArguments(input) {
78
+ const filters = compactObject({
79
+ ...optionalRecord(input.filters),
80
+ last_edited_date_range: optionalRecord(input.last_edited_date_range),
81
+ created_date_range: optionalRecord(input.created_date_range),
82
+ created_by_user_ids: optionalStringArray(input.created_by_user_ids),
83
+ edited_by_user_ids: optionalStringArray(input.edited_by_user_ids),
84
+ });
85
+ return compactObject({
86
+ query: optionalRawString(input.query) ?? "",
87
+ sort: optionalString(input.sort),
88
+ page_size: optionalInteger(input.page_size),
89
+ max_highlight_length: optionalInteger(input.max_highlight_length),
90
+ filters: Object.keys(filters).length > 0 ? filters : undefined,
91
+ });
92
+ }
93
+ async function listNotionTools(client, signal) {
94
+ const tools = [];
95
+ const cursors = new Set();
96
+ let cursor;
97
+ do {
98
+ const page = await client.listTools(cursor === undefined ? {} : { cursor }, { signal });
99
+ tools.push(...page.tools);
100
+ cursor = page.nextCursor;
101
+ if (cursor !== undefined) {
102
+ if (cursors.has(cursor))
103
+ throw providerResponseError("Notion MCP tools/list returned a repeated cursor.");
104
+ cursors.add(cursor);
105
+ }
106
+ } while (cursor !== undefined);
107
+ return tools;
108
+ }
109
+ async function callNotionTool(context, name, input) {
110
+ const result = await withNotionMcpClient(context, "execute", (client, signal) => client.callTool({ name, arguments: input }, { signal }));
111
+ return readNotionToolResult(name, result);
112
+ }
113
+ /** The text blocks joined are the raw answer; structured content, when the server sends it, is the value. */
114
+ function readNotionToolResult(name, result) {
115
+ const record = requiredResponseRecord(result, "Notion MCP result");
116
+ if (record.isError === true)
117
+ throw notionToolError(name, record);
118
+ const texts = looseArray(record.content).flatMap((block) => {
119
+ const item = optionalRecord(block);
120
+ return item?.type === "text" ? [requiredRawString(item.text, "Notion MCP text", providerResponseError)] : [];
121
+ });
122
+ const structured = optionalRecord(record.structuredContent);
123
+ if (texts.length === 0 && structured === undefined)
124
+ throw providerResponseError("Notion MCP returned no content.");
125
+ const raw = texts.length > 0 ? texts.join("\n") : JSON.stringify(structured);
126
+ return { raw, value: structured ?? decodeNotionToolText(raw) };
127
+ }
128
+ /**
129
+ * A tool error. Notion returns a rate limit it does not wait out as a tool error whose `structuredContent.error`
130
+ * carries `code: "rate_limited"` and, when known, `retry_after_seconds`; that one is a 429, the rest a 502.
131
+ */
132
+ function notionToolError(name, record) {
133
+ const error = optionalRecord(optionalRecord(record.structuredContent)?.error);
134
+ if (error?.code === "rate_limited") {
135
+ const retryAfter = optionalNumber(error.retry_after_seconds);
136
+ const wait = retryAfter === undefined ? "" : `; retry after ${retryAfter} seconds`;
137
+ return new ProviderRequestError(429, `Notion MCP tool ${name} was rate limited${wait}.`, record);
138
+ }
139
+ return new ProviderRequestError(502, `Notion MCP tool ${name} failed.`, record);
140
+ }
141
+ function withNotionMcpClient(context, phase, run) {
142
+ return runProviderRequest({ signal: context.signal, label: "Notion MCP" }, (signal) => withMcpClient({
143
+ endpoint: new URL(notionMcpEndpoint),
144
+ transport: "streamable_http",
145
+ fetcher: context.fetcher,
146
+ headers: { authorization: `Bearer ${context.accessToken}`, "user-agent": providerUserAgent },
147
+ redirect: "manual",
148
+ signal,
149
+ mapError: (error) => mapNotionMcpError(error, phase),
150
+ }, (client) => run(client, signal)));
151
+ }
152
+ /**
153
+ * Sign-in reports a rejected token as invalid input (400); a run reports it as an expired authorization (401).
154
+ * Anything unrecognized is left to runProviderRequest, which tells a timeout (504) from a failure (502).
155
+ */
156
+ function mapNotionMcpError(error, phase) {
157
+ if (error instanceof ProviderRequestError)
158
+ return error;
159
+ const credentialStatus = phase === "validate" ? 400 : 401;
160
+ if (error instanceof UnauthorizedError) {
161
+ return new ProviderRequestError(credentialStatus, "Notion MCP credential is invalid or expired.");
162
+ }
163
+ if (error instanceof SdkHttpError) {
164
+ const status = error.status;
165
+ if (status === 401 || status === 403) {
166
+ return new ProviderRequestError(credentialStatus, "Notion MCP credential is invalid or expired.");
167
+ }
168
+ // The SDK error carries the response body; only the status is reported so a token echoed in it never is.
169
+ const providerStatus = 400 <= status && status < 500 ? status : 502;
170
+ return new ProviderRequestError(providerStatus, `Notion MCP request failed with HTTP ${status}.`);
171
+ }
172
+ if (error instanceof ProtocolError) {
173
+ return new ProviderRequestError(502, `Notion MCP request failed: ${error.message}`);
174
+ }
175
+ return error;
176
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Readers for the text envelopes Notion's beta MCP tools answer in, as they were recorded live. Every reader is
3
+ * defensive: a field the server omits is absent from the typed result, an unrecognized shape yields an empty
4
+ * result, and nothing here throws, because the caller keeps the raw text beside the typed fields.
5
+ */
6
+ interface NotionMcpUser {
7
+ id?: string;
8
+ name?: string;
9
+ email?: string;
10
+ type?: string;
11
+ }
12
+ interface NotionMcpSearchResult {
13
+ id?: string;
14
+ title?: string;
15
+ url?: string;
16
+ type?: string;
17
+ timestamp?: string;
18
+ path?: string;
19
+ }
20
+ interface NotionMcpSearch {
21
+ type: string | undefined;
22
+ results: NotionMcpSearchResult[];
23
+ notices: string[];
24
+ }
25
+ interface NotionMcpPage {
26
+ title?: string;
27
+ url?: string;
28
+ page_last_edited_at?: string;
29
+ properties?: string;
30
+ content?: string;
31
+ truncated: boolean;
32
+ }
33
+ interface NotionMcpComment {
34
+ id?: string;
35
+ discussion_id?: string;
36
+ plain_text?: string;
37
+ created_time?: string;
38
+ created_by?: NotionMcpUser;
39
+ }
40
+ interface NotionMcpToolRestriction {
41
+ parameter: string;
42
+ reason: string;
43
+ }
44
+ interface NotionMcpToolAccess {
45
+ tool: string;
46
+ status: string | undefined;
47
+ restricted_parameters: NotionMcpToolRestriction[];
48
+ upgrade_url: string | undefined;
49
+ full_version_url: string | undefined;
50
+ landing_page_url: string | undefined;
51
+ landing_page_action: string | undefined;
52
+ }
53
+ /** Notion's tools answer JSON in a text block on most calls and prose or XML fragments on the rest. */
54
+ export declare function decodeNotionToolText(text: string): unknown;
55
+ /**
56
+ * The connected user as notion-get-users answers `user_id: "self"`: the first listed user of
57
+ * `{results: [{type, id, name, email}], has_more}` that carries an ID or an email, or the answer itself when it is a
58
+ * single user.
59
+ */
60
+ export declare function parseNotionSelf(value: unknown): NotionMcpUser | undefined;
61
+ /**
62
+ * Search results plus the server's notices, such as a filter dropped on the connected plan, and the search `type`
63
+ * the server reports: `workspace_search`, or `ai_search` when it routed a keyword query to Notion AI search.
64
+ */
65
+ export declare function parseNotionSearch(value: unknown): NotionMcpSearch;
66
+ /**
67
+ * The page envelope notion-fetch answers: a preamble line, then
68
+ * `<page url="..."><properties>...</properties><content>MARKDOWN</content></page>`, either bare in the text block or
69
+ * under `text` of a `{title, url, page_last_edited_at, text}` object. A page the server cut says so with a
70
+ * `truncated` flag on the object or on the tag.
71
+ */
72
+ export declare function parseNotionPage(value: unknown): NotionMcpPage;
73
+ /**
74
+ * Comments as notion-get-comments answers them, in both recorded shapes: the structured
75
+ * `{discussions: [{id, comments: [{id, plain_text, created_time, created_by}]}]}` (or a flat `{comments: [...]}`),
76
+ * or the XML twin `<discussion id><comment id author author_id created_time>text</comment></discussion>`.
77
+ */
78
+ export declare function parseNotionComments(value: unknown): NotionMcpComment[];
79
+ /**
80
+ * The plan report notion-get-tool-access answers: `{current_tool_access: {<tool>: {status, restricted_parameters?,
81
+ * upgrade_url?, full_version_url?, landing_page_url?, landing_page_action?}}}`, keyed by each tool's base name
82
+ * (`search`, `ai_search`), where `restricted_parameters` maps a parameter path such as `filters.title_only` to the
83
+ * reason it is unavailable. The reason is kept as text and never interpreted.
84
+ */
85
+ export declare function parseNotionToolAccess(value: unknown): NotionMcpToolAccess[];
86
+ export {};