@oomol-lab/open-connector 1.6.5 → 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 (243) hide show
  1. package/assets/open-connector/catalog/apps/aisa.json +6138 -312
  2. package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
  3. package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
  4. package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
  5. package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
  6. package/assets/open-connector/catalog/apps/framejet.json +273 -0
  7. package/assets/open-connector/catalog/apps/futunn.json +2967 -0
  8. package/assets/open-connector/catalog/apps/gmail.json +225 -0
  9. package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
  10. package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
  11. package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
  12. package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
  13. package/assets/open-connector/catalog/apps/googlechat.json +694 -7
  14. package/assets/open-connector/catalog/apps/googledocs.json +4 -1
  15. package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
  16. package/assets/open-connector/catalog/apps/granola.json +23 -0
  17. package/assets/open-connector/catalog/apps/keepa.json +3534 -987
  18. package/assets/open-connector/catalog/apps/linear.json +17 -0
  19. package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
  20. package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
  21. package/assets/open-connector/catalog/apps/notion.json +446 -1
  22. package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
  23. package/assets/open-connector/catalog/apps/odoo.json +619 -0
  24. package/assets/open-connector/catalog/apps/outlook.json +30 -8
  25. package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
  26. package/assets/open-connector/catalog/apps/postiz.json +334 -0
  27. package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
  28. package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
  29. package/assets/open-connector/catalog/apps/slack.json +223 -0
  30. package/assets/open-connector/catalog/apps/slackbot.json +214 -0
  31. package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
  32. package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
  33. package/assets/open-connector/catalog/apps/youtube.json +4 -1
  34. package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
  35. package/assets/open-connector/catalog/apps-index.json +1 -1
  36. package/package.json +1 -1
  37. package/src/core/cast.d.ts +10 -0
  38. package/src/core/cast.js +14 -0
  39. package/src/core/guarded-fetch.d.ts +6 -3
  40. package/src/core/guarded-fetch.js +6 -3
  41. package/src/core/provider-scenarios.js +67 -13
  42. package/src/core/request.js +4 -1
  43. package/src/core/types.d.ts +1 -1
  44. package/src/mcp.js +10 -7
  45. package/src/oauth/oauth-client-config-service.d.ts +16 -1
  46. package/src/oauth/oauth-client-config-service.js +59 -3
  47. package/src/oauth/oauth-flow-service.d.ts +6 -0
  48. package/src/oauth/oauth-flow-service.js +4 -2
  49. package/src/providers/17track/runtime.js +1 -1
  50. package/src/providers/action-contracts.generated.d.ts +17 -5
  51. package/src/providers/aifinmarket/runtime.js +1 -1
  52. package/src/providers/aisa/executors.js +734 -46
  53. package/src/providers/aliyun_ack/runtime.js +1 -1
  54. package/src/providers/aliyun_sls/runtime.js +1 -1
  55. package/src/providers/app_store_server/runtime-helpers.js +1 -1
  56. package/src/providers/baizhi_mcp/actions.d.ts +2 -0
  57. package/src/providers/baizhi_mcp/actions.js +40 -0
  58. package/src/providers/baizhi_mcp/executors.d.ts +3 -0
  59. package/src/providers/baizhi_mcp/executors.js +102 -0
  60. package/src/providers/bazhuayu/runtime.js +2 -2
  61. package/src/providers/bilibili/runtime.js +2 -4
  62. package/src/providers/bilibili/upload.js +9 -7
  63. package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
  64. package/src/providers/cloudflare_dns/executors.js +1 -1
  65. package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
  66. package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
  67. package/src/providers/cloudflare_dns/runtime.js +1 -1
  68. package/src/providers/cloudflare_r2/executors.js +1 -1
  69. package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
  70. package/src/providers/cloudflare_r2/runtime.js +1 -1
  71. package/src/providers/cloudflare_worker/executors.js +1 -1
  72. package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
  73. package/src/providers/cloudflare_worker/runtime.js +1 -1
  74. package/src/providers/dingtalk_mcp/runtime.js +1 -1
  75. package/src/providers/discordbot/executors.d.ts +0 -9
  76. package/src/providers/discordbot/executors.js +9 -101
  77. package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
  78. package/src/providers/discordbot/runtime-guilds.js +464 -0
  79. package/src/providers/discordbot/runtime.d.ts +42 -0
  80. package/src/providers/discordbot/runtime.js +124 -0
  81. package/src/providers/drawio_mcp/executors.d.ts +3 -0
  82. package/src/providers/drawio_mcp/executors.js +19 -0
  83. package/src/providers/drawio_mcp/runtime.d.ts +8 -0
  84. package/src/providers/drawio_mcp/runtime.js +175 -0
  85. package/src/providers/fastmoss_mcp/executors.js +2 -2
  86. package/src/providers/finchina/executors.js +1 -1
  87. package/src/providers/framejet/executors.d.ts +4 -0
  88. package/src/providers/framejet/executors.js +19 -0
  89. package/src/providers/framejet/runtime.d.ts +16 -0
  90. package/src/providers/framejet/runtime.js +148 -0
  91. package/src/providers/futunn/actions.d.ts +2 -0
  92. package/src/providers/futunn/actions.js +152 -0
  93. package/src/providers/futunn/actions.research.d.ts +2 -0
  94. package/src/providers/futunn/actions.research.js +423 -0
  95. package/src/providers/futunn/actions.trading.d.ts +2 -0
  96. package/src/providers/futunn/actions.trading.js +204 -0
  97. package/src/providers/futunn/executors.d.ts +3 -0
  98. package/src/providers/futunn/executors.js +24 -0
  99. package/src/providers/futunn/runtime-research.d.ts +3 -0
  100. package/src/providers/futunn/runtime-research.js +133 -0
  101. package/src/providers/futunn/runtime-trading.d.ts +3 -0
  102. package/src/providers/futunn/runtime-trading.js +39 -0
  103. package/src/providers/futunn/runtime.d.ts +2 -0
  104. package/src/providers/futunn/runtime.js +50 -0
  105. package/src/providers/futunn/transport.d.ts +11 -0
  106. package/src/providers/futunn/transport.js +76 -0
  107. package/src/providers/gildata/executors.js +1 -1
  108. package/src/providers/gmail/executors.js +3 -3
  109. package/src/providers/gmail/message.d.ts +6 -8
  110. package/src/providers/gmail/message.js +4 -0
  111. package/src/providers/gmail/scopes.d.ts +32 -0
  112. package/src/providers/gmail/scopes.js +37 -0
  113. package/src/providers/google_analytics/executors.js +2 -2
  114. package/src/providers/google_analytics/scopes.js +6 -1
  115. package/src/providers/google_bigquery/executors.js +2 -2
  116. package/src/providers/google_bigquery/scopes.js +2 -0
  117. package/src/providers/google_search_console/executors.js +2 -2
  118. package/src/providers/google_search_console/scopes.js +2 -0
  119. package/src/providers/googleads/executors.js +1 -1
  120. package/src/providers/googleads/scopes.d.ts +1 -3
  121. package/src/providers/googleads/scopes.js +2 -4
  122. package/src/providers/googlecalendar/executors.js +1 -1
  123. package/src/providers/googlecalendar/runtime-shared.js +2 -2
  124. package/src/providers/googlecalendar/scopes.js +2 -0
  125. package/src/providers/googlechat/executors.d.ts +1 -3
  126. package/src/providers/googlechat/executors.js +219 -28
  127. package/src/providers/googlechat/members.d.ts +55 -0
  128. package/src/providers/googlechat/members.js +293 -0
  129. package/src/providers/googlechat/runtime.d.ts +16 -0
  130. package/src/providers/googlechat/runtime.js +25 -0
  131. package/src/providers/googlechat/scopes.d.ts +22 -3
  132. package/src/providers/googlechat/scopes.js +30 -7
  133. package/src/providers/googledocs/executors.js +1 -1
  134. package/src/providers/googledrive/executors.js +2 -2
  135. package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
  136. package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
  137. package/src/providers/googledrive/runtime-collaboration.js +1 -1
  138. package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
  139. package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
  140. package/src/providers/googleforms/executors.js +2 -2
  141. package/src/providers/googleforms/scopes.d.ts +0 -3
  142. package/src/providers/googleforms/scopes.js +2 -6
  143. package/src/providers/googlemeet/executors.js +2 -2
  144. package/src/providers/googlemeet/scopes.d.ts +0 -3
  145. package/src/providers/googlemeet/scopes.js +2 -6
  146. package/src/providers/googlesheets/executors.js +1 -1
  147. package/src/providers/googlesheets/scopes.js +2 -0
  148. package/src/providers/googleslides/executors.js +2 -2
  149. package/src/providers/googleslides/scopes.d.ts +0 -3
  150. package/src/providers/googleslides/scopes.js +2 -6
  151. package/src/providers/googletasks/executors.js +2 -2
  152. package/src/providers/googletasks/scopes.d.ts +0 -3
  153. package/src/providers/googletasks/scopes.js +2 -6
  154. package/src/providers/granola/actions.js +2 -0
  155. package/src/providers/granola/runtime-mcp.js +2 -0
  156. package/src/providers/granola/runtime.js +2 -2
  157. package/src/providers/indiegogo/executors.js +1 -1
  158. package/src/providers/investoday_mcp/executors.js +2 -2
  159. package/src/providers/iyunbiao/runtime.js +2 -2
  160. package/src/providers/keepa/actions.d.ts +1 -1
  161. package/src/providers/keepa/actions.js +371 -149
  162. package/src/providers/keepa/deal-attributes.d.ts +1 -0
  163. package/src/providers/keepa/deal-attributes.js +26 -0
  164. package/src/providers/keepa/runtime.js +212 -18
  165. package/src/providers/kingdee/executors.js +1 -1
  166. package/src/providers/kingdee/runtime.js +2 -2
  167. package/src/providers/kuaidi100/runtime.js +1 -1
  168. package/src/providers/linear/executors.js +12 -9
  169. package/src/providers/linear_mcp/actions.d.ts +47 -0
  170. package/src/providers/linear_mcp/actions.js +102 -0
  171. package/src/providers/linear_mcp/endpoints.d.ts +3 -0
  172. package/src/providers/linear_mcp/endpoints.js +3 -0
  173. package/src/providers/linear_mcp/executors.d.ts +3 -0
  174. package/src/providers/linear_mcp/executors.js +9 -0
  175. package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
  176. package/src/providers/linear_mcp/runtime-mcp.js +238 -0
  177. package/src/providers/lingxing_mcp/runtime.js +1 -1
  178. package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
  179. package/src/providers/metabase/mcp-runtime.js +1 -1
  180. package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
  181. package/src/providers/moneyforward_mcp/executors.js +11 -0
  182. package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
  183. package/src/providers/moneyforward_mcp/manifest.js +204 -0
  184. package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
  185. package/src/providers/moneyforward_mcp/runtime.js +296 -0
  186. package/src/providers/notion/executors.js +39 -3
  187. package/src/providers/notion_mcp/endpoints.d.ts +3 -0
  188. package/src/providers/notion_mcp/endpoints.js +3 -0
  189. package/src/providers/notion_mcp/executors.d.ts +3 -0
  190. package/src/providers/notion_mcp/executors.js +176 -0
  191. package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
  192. package/src/providers/notion_mcp/mcp-response.js +314 -0
  193. package/src/providers/odoo/executors.d.ts +3 -0
  194. package/src/providers/odoo/executors.js +32 -0
  195. package/src/providers/odoo/runtime.d.ts +16 -0
  196. package/src/providers/odoo/runtime.js +140 -0
  197. package/src/providers/outlook/executors.js +27 -6
  198. package/src/providers/outlook/microsoft-graph.js +17 -6
  199. package/src/providers/outlook_calendar/executors.js +24 -7
  200. package/src/providers/patsnap_mcp/executors.js +2 -2
  201. package/src/providers/postiz/executors.d.ts +4 -0
  202. package/src/providers/postiz/executors.js +83 -0
  203. package/src/providers/provider-runtime.d.ts +15 -0
  204. package/src/providers/provider-runtime.js +57 -4
  205. package/src/providers/proworkflow/executors.d.ts +4 -0
  206. package/src/providers/proworkflow/executors.js +135 -0
  207. package/src/providers/registry.generated.js +12 -0
  208. package/src/providers/scrape_creators/catalog.js +1 -1
  209. package/src/providers/scrape_creators/runtime.js +1 -1
  210. package/src/providers/sellersprite_mcp/executors.js +2 -2
  211. package/src/providers/sellersprite_mcp/official-actions.js +3 -2
  212. package/src/providers/sentry/runtime.js +6 -7
  213. package/src/providers/slack/actions.js +52 -3
  214. package/src/providers/slack/runtime.js +58 -21
  215. package/src/providers/slickdeals/runtime.js +1 -1
  216. package/src/providers/sorftime_mcp/executors.js +2 -2
  217. package/src/providers/tikhub/bounded-response.d.ts +1 -1
  218. package/src/providers/tikhub/bounded-response.js +8 -34
  219. package/src/providers/tikhub/endpoint-catalog.js +1 -1
  220. package/src/providers/tikhub/runtime.js +2 -2
  221. package/src/providers/tongdaxin/executors.js +79 -15
  222. package/src/providers/uspto/executors.js +1 -1
  223. package/src/providers/waffo/executors.js +1 -1
  224. package/src/providers/waffo/runtime.js +1 -1
  225. package/src/providers/wecom_mcp/runtime.js +1 -1
  226. package/src/providers/wisburg/executors.d.ts +4 -0
  227. package/src/providers/wisburg/executors.js +13 -0
  228. package/src/providers/wisburg/runtime.d.ts +12 -0
  229. package/src/providers/wisburg/runtime.js +133 -0
  230. package/src/providers/xydc_mcp/executors.js +2 -2
  231. package/src/providers/youtube/executors.js +1 -1
  232. package/src/providers/youtube/runtime.js +1 -1
  233. package/src/providers/zerotier/executors.d.ts +9 -0
  234. package/src/providers/zerotier/executors.js +629 -0
  235. package/src/providers/zerotier/runtime.d.ts +54 -0
  236. package/src/providers/zerotier/runtime.js +207 -0
  237. package/src/server/api/openapi.js +12 -2
  238. package/src/server/api/runtime-api.d.ts +8 -1
  239. package/src/server/api/runtime-api.js +19 -1
  240. package/src/server/connect-server.js +10 -2
  241. package/src/providers/sentry/scopes.d.ts +0 -1
  242. package/src/providers/sentry/scopes.js +0 -7
  243. /package/src/providers/{cloudflare-current-user.d.ts → cloudflare_dns/runtime-user.d.ts} +0 -0
@@ -0,0 +1,175 @@
1
+ import { sha256Hex } from "../../core/aws-sigv4.js";
2
+ import { looseArray, optionalInteger, optionalNumber, optionalRawString, optionalRecord, optionalString, } from "../../core/cast.js";
3
+ import { assertPublicHttpUrl, isPrivateNetworkAccessAllowed } from "../../core/request.js";
4
+ import { callMcpTool, listMcpTools } from "../mcp-tools.js";
5
+ import { parseProviderJsonBodyText, providerInputError, providerResponseError, requiredInputString, } from "../provider-runtime.js";
6
+ const hostedEndpoint = "https://mcp.draw.io/mcp";
7
+ const defaultShapeLimit = 10;
8
+ // The two create_diagram tool errors that judge the submitted source; anything else is a server failure.
9
+ const sourceVerdictPrefixes = ["Could not extract draw.io XML", "Provide exactly one of"];
10
+ const errorsHeading = "ERRORS (will cause rendering issues):\n- ";
11
+ const warningsHeading = "WARNINGS (may cause issues):\n- ";
12
+ export const drawioMcpActionHandlers = {
13
+ async create_diagram(input, options) {
14
+ const result = await callMcpTool({
15
+ ...options,
16
+ toolName: "create_diagram",
17
+ arguments: { mermaid: optionalString(input.mermaid), xml: optionalString(input.xml) },
18
+ transformToolResult: rejectDiagramSource,
19
+ });
20
+ const texts = readTextContent(result);
21
+ const editorUrl = readEditorUrl(texts);
22
+ if (editorUrl === undefined) {
23
+ throw providerResponseError("draw.io MCP create_diagram response did not include an editor link; a self-hosted server must be jgraph/drawio-mcp mcp-app-server 1.0.4 or later");
24
+ }
25
+ return { editorUrl, ...readFindings(texts) };
26
+ },
27
+ async search_shapes(input, options) {
28
+ const result = await callMcpTool({
29
+ ...options,
30
+ toolName: "search_shapes",
31
+ arguments: {
32
+ query: requiredInputString(input.query, "query"),
33
+ limit: optionalInteger(input.limit) ?? defaultShapeLimit,
34
+ },
35
+ });
36
+ return { shapes: readShapes(result) };
37
+ },
38
+ };
39
+ /** Resolve the MCP server a connection points at; a blank endpoint selects the hosted draw.io server. */
40
+ export function createDrawioMcpContext(values, fetcher, signal) {
41
+ return {
42
+ endpoint: normalizeDrawioMcpEndpoint(values.mcpEndpoint),
43
+ service: "draw.io",
44
+ fetcher,
45
+ signal,
46
+ terminateSession: true,
47
+ maxResponseBytes: 4 * 1024 * 1024,
48
+ // tools/list is about 64 KB because create_diagram embeds draw.io's drawing reference in its description.
49
+ toolListMaxBytes: 1024 * 1024,
50
+ toolListMaxPages: 5,
51
+ toolListMaxTools: 100,
52
+ };
53
+ }
54
+ /** Confirm the endpoint serves the draw.io tools and derive a stable identity from the endpoint. */
55
+ export async function validateDrawioMcpCredential(values, fetcher, signal) {
56
+ const options = createDrawioMcpContext(values, fetcher, signal);
57
+ const discovered = new Set((await listMcpTools(options)).map((tool) => tool.name));
58
+ const missing = Object.keys(drawioMcpActionHandlers).filter((toolName) => !discovered.has(toolName));
59
+ if (missing.length > 0) {
60
+ throw providerInputError(`mcpEndpoint does not serve the draw.io MCP tools: missing ${missing.join(", ")}`);
61
+ }
62
+ return {
63
+ profile: {
64
+ accountId: `drawio_mcp:mcp:${sha256Hex(options.endpoint).slice(0, 16)}`,
65
+ displayName: `draw.io MCP · ${new URL(options.endpoint).host}`,
66
+ },
67
+ grantedScopes: [],
68
+ metadata: { mcpEndpoint: options.endpoint },
69
+ };
70
+ }
71
+ function normalizeDrawioMcpEndpoint(value) {
72
+ const allowPrivateNetwork = isPrivateNetworkAccessAllowed();
73
+ const url = assertPublicHttpUrl(optionalString(value) ?? hostedEndpoint, {
74
+ fieldName: "mcpEndpoint",
75
+ createError: providerInputError,
76
+ allowPrivateNetwork,
77
+ });
78
+ if (url.username || url.password) {
79
+ throw providerInputError("mcpEndpoint must not include a username or password");
80
+ }
81
+ if (url.protocol === "http:" && !allowPrivateNetwork) {
82
+ throw providerInputError("mcpEndpoint must use https unless private-network access is enabled");
83
+ }
84
+ url.search = "";
85
+ url.hash = "";
86
+ return url.toString();
87
+ }
88
+ // Also drops structured content: the editor link and findings only ever arrive as text blocks,
89
+ // which the shared result normalizer would otherwise discard in favour of structuredContent.
90
+ function rejectDiagramSource(result) {
91
+ const message = "content" in result && result.isError ? readTextContent(result).join("\n") : "";
92
+ if (sourceVerdictPrefixes.some((prefix) => message.startsWith(prefix))) {
93
+ throw providerInputError(`draw.io rejected the diagram source: ${message.slice(0, 300)}`);
94
+ }
95
+ return { ...result, structuredContent: undefined };
96
+ }
97
+ // draw.io puts the editor link on a line of its own after the source echo, which is single-line JSON, and after
98
+ // the XML findings, which quote attribute values from the source verbatim, line breaks included. Skipping the
99
+ // findings and taking the last such line keeps a link written inside the submitted source from being returned instead.
100
+ function readEditorUrl(texts) {
101
+ return texts
102
+ .filter((text) => !isFindingsText(text))
103
+ .flatMap((text) => text.split("\n"))
104
+ .findLast((line) => line.startsWith("https://") && line.includes("#create="));
105
+ }
106
+ // draw.io reports XML problems in one text block of "ERRORS (...):\n- item" and "WARNINGS (...):\n- item" sections.
107
+ function isFindingsText(text) {
108
+ return text.startsWith("ERRORS ") || text.startsWith("WARNINGS ");
109
+ }
110
+ function readTextContent(result) {
111
+ return looseArray(optionalRecord(result)?.content).flatMap((item) => {
112
+ const content = optionalRecord(item);
113
+ const text = content?.type === "text" ? optionalRawString(content.text) : undefined;
114
+ return text === undefined ? [] : [text];
115
+ });
116
+ }
117
+ // Findings items quote attribute values verbatim, so blank lines and line starts can come from the source. Only
118
+ // draw.io's exact layout splits the block: each heading is followed by the items joined with "\n- ", and a blank
119
+ // line separates the errors section from the warnings section. The layout leaves no way to tell a quoted value from
120
+ // that structure: a value that itself contains "\n- " still reads as two items, and one that contains the whole
121
+ // "\n\nWARNINGS (may cause issues):\n- " separator can move items between errors and warnings. The editor link is
122
+ // unaffected, since readEditorUrl skips the findings block as a whole.
123
+ function readFindings(texts) {
124
+ const text = texts.find(isFindingsText) ?? "";
125
+ if (text.startsWith(warningsHeading)) {
126
+ return { errors: [], warnings: splitFindings(text.slice(warningsHeading.length)) };
127
+ }
128
+ if (!text.startsWith(errorsHeading)) {
129
+ return { errors: [], warnings: [] };
130
+ }
131
+ const body = text.slice(errorsHeading.length);
132
+ const separator = body.lastIndexOf(`\n\n${warningsHeading}`);
133
+ if (separator < 0) {
134
+ return { errors: splitFindings(body), warnings: [] };
135
+ }
136
+ return {
137
+ errors: splitFindings(body.slice(0, separator)),
138
+ warnings: splitFindings(body.slice(separator + 2 + warningsHeading.length)),
139
+ };
140
+ }
141
+ // draw.io never reports an empty finding, so an empty piece can only come from a quoted value that contains the
142
+ // separator, such as one ending in "\n- ", and is dropped.
143
+ function splitFindings(section) {
144
+ return section.split("\n- ").filter((item) => item !== "");
145
+ }
146
+ // Newer draw.io servers return structured shapes; the hosted server returns a JSON array as text.
147
+ function readShapes(result) {
148
+ const structured = optionalRecord(result)?.shapes;
149
+ const payload = structured === undefined ? parseShapeText(readTextContent(result)[0]) : structured;
150
+ if (!Array.isArray(payload)) {
151
+ throw providerResponseError("draw.io MCP search_shapes returned an unexpected payload");
152
+ }
153
+ return payload.map(readShape);
154
+ }
155
+ function parseShapeText(text) {
156
+ if (text === undefined) {
157
+ throw providerResponseError("draw.io MCP search_shapes response did not include any text");
158
+ }
159
+ if (text.startsWith("No shapes found"))
160
+ return [];
161
+ return parseProviderJsonBodyText(text, {
162
+ emptyBody: [],
163
+ invalidJsonMessage: `draw.io MCP search_shapes returned an unexpected payload: ${text.slice(0, 200)}`,
164
+ });
165
+ }
166
+ function readShape(value) {
167
+ const shape = optionalRecord(value);
168
+ const style = optionalRawString(shape?.style);
169
+ const width = optionalNumber(shape?.w);
170
+ const height = optionalNumber(shape?.h);
171
+ if (!style || width === undefined || height === undefined) {
172
+ throw providerResponseError("draw.io MCP search_shapes returned a shape without a style or size");
173
+ }
174
+ return { title: optionalRawString(shape?.title) ?? "", style, width, height };
175
+ }
@@ -25,7 +25,7 @@ export const proxy = defineProviderProxy({
25
25
  baseUrl: origin,
26
26
  auth: { type: "api_key_query", name: "api_key" },
27
27
  skipDnsValidation: true,
28
- redirect: "error",
28
+ redirect: "manual",
29
29
  allowedEndpoint(value) {
30
30
  const url = new URL(value, origin);
31
31
  return (url.origin === origin &&
@@ -57,7 +57,7 @@ function connectionInput(key, fetcher, signal) {
57
57
  service: "FastMoss",
58
58
  fetcher,
59
59
  signal,
60
- redirect: "error",
60
+ redirect: "manual",
61
61
  terminateSession: true,
62
62
  maxResponseBytes: 16 * 1024 * 1024,
63
63
  };
@@ -98,7 +98,7 @@ function mcpInput(apiKey, fetcher, signal) {
98
98
  service: "FinChina",
99
99
  headers: { "x-api-key": apiKey },
100
100
  fetcher,
101
- redirect: "error",
101
+ redirect: "manual",
102
102
  terminateSession: true,
103
103
  };
104
104
  }
@@ -0,0 +1,4 @@
1
+ import type { CredentialValidators, ProviderExecutors, ProviderProxyExecutor } from "../../core/types.ts";
2
+ export declare const executors: ProviderExecutors;
3
+ export declare const proxy: ProviderProxyExecutor;
4
+ export declare const credentialValidators: CredentialValidators;
@@ -0,0 +1,19 @@
1
+ import { defineApiKeyProviderExecutors, defineProviderProxy } from "../provider-runtime.js";
2
+ import { framejetActionHandlers, framejetApiBaseUrl, framejetCaptureTimeoutMs, readFramejetError, validateFramejetApiKey, } from "./runtime.js";
3
+ const service = "framejet";
4
+ export const executors = defineApiKeyProviderExecutors(service, framejetActionHandlers, {
5
+ skipDnsValidation: true,
6
+ });
7
+ export const proxy = defineProviderProxy({
8
+ service,
9
+ baseUrl: framejetApiBaseUrl,
10
+ auth: { type: "api_key_header", name: "X-Api-Key" },
11
+ readError: readFramejetError,
12
+ skipDnsValidation: true,
13
+ timeoutMs: framejetCaptureTimeoutMs,
14
+ });
15
+ export const credentialValidators = {
16
+ apiKey(input, { fetcher, signal }) {
17
+ return validateFramejetApiKey(input.apiKey, fetcher, signal);
18
+ },
19
+ };
@@ -0,0 +1,16 @@
1
+ import type { CredentialValidationResult } from "../../core/types.ts";
2
+ import type { ApiKeyProviderContext, ProviderActionHandlers, ProviderFetch } from "../provider-runtime.ts";
3
+ import { ProviderRequestError } from "../provider-runtime.ts";
4
+ export declare const framejetApiBaseUrl = "https://framejet.dev";
5
+ export declare const framejetCaptureTimeoutMs = 90000;
6
+ type FramejetActionHandler = (input: Record<string, unknown>, context: ApiKeyProviderContext) => Promise<unknown>;
7
+ export declare const framejetActionHandlers: ProviderActionHandlers<"framejet", FramejetActionHandler>;
8
+ /**
9
+ * Check a key against GET /v1/me, which does not spend a screenshot.
10
+ */
11
+ export declare function validateFramejetApiKey(apiKey: string, fetcher: ProviderFetch, signal?: AbortSignal): Promise<CredentialValidationResult>;
12
+ /**
13
+ * Map a failed Framejet response for both actions and the proxy, so a spent quota reads the same on either path.
14
+ */
15
+ export declare function readFramejetError(response: Response): Promise<ProviderRequestError>;
16
+ export {};
@@ -0,0 +1,148 @@
1
+ import { createHmac } from "node:crypto";
2
+ import { optionalIntegerOrNull, optionalRecord, optionalString } from "../../core/cast.js";
3
+ import { readBoundedResponseBytes } from "../../core/request.js";
4
+ import { providerInputError, providerResponseError, providerUserAgent, readProviderErrorTextBody, ProviderRequestError, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
5
+ export const framejetApiBaseUrl = "https://framejet.dev";
6
+ // A capture may wait for slow pages, page steps and goal mode, so it gets more than the default budget.
7
+ // The proxy shares this budget, since GET /v1/take is the capture endpoint on both paths.
8
+ export const framejetCaptureTimeoutMs = 90_000;
9
+ const framejetCaptureParams = ["format", "full_page", "width", "height", "dpr", "clean", "delay", "actions"];
10
+ // Framejet caps each value at 200 characters, which the schema enforces, and the joined list at 1000.
11
+ const framejetMaxValuesLength = 1000;
12
+ export const framejetActionHandlers = {
13
+ take_screenshot(input, context) {
14
+ return takeFramejetScreenshot(input, context);
15
+ },
16
+ create_signed_url(input, context) {
17
+ return createFramejetSignedUrl(input, context);
18
+ },
19
+ };
20
+ /**
21
+ * Check a key against GET /v1/me, which does not spend a screenshot.
22
+ */
23
+ export async function validateFramejetApiKey(apiKey, fetcher, signal) {
24
+ const key = requiredInputString(apiKey, "apiKey");
25
+ await runProviderRequest({ signal, label: "Framejet" }, async (requestSignal) => {
26
+ const response = await fetcher(new URL("/v1/me", framejetApiBaseUrl), {
27
+ headers: framejetHeaders(key),
28
+ signal: requestSignal,
29
+ });
30
+ if (!response.ok) {
31
+ const error = await readFramejetError(response);
32
+ // A rejected key is a field error on the connect form, not a reconnect prompt.
33
+ throw response.status === 401 || response.status === 403 ? providerInputError(error.message) : error;
34
+ }
35
+ });
36
+ return {
37
+ profile: {
38
+ accountId: "api_key",
39
+ displayName: "Framejet API Key",
40
+ },
41
+ grantedScopes: [],
42
+ metadata: {
43
+ apiBaseUrl: framejetApiBaseUrl,
44
+ validationEndpoint: "/v1/me",
45
+ },
46
+ };
47
+ }
48
+ async function takeFramejetScreenshot(input, context) {
49
+ const transitFiles = context.transitFiles;
50
+ if (!transitFiles) {
51
+ throw new ProviderRequestError(500, "take_screenshot requires transit file storage");
52
+ }
53
+ const query = buildFramejetQuery(input, ["goal", "values", "cache"]);
54
+ if ([...(query.get("values") ?? "")].length > framejetMaxValuesLength) {
55
+ throw providerInputError(`values must total at most ${framejetMaxValuesLength} characters, including the | separators`);
56
+ }
57
+ const url = new URL("/v1/take", framejetApiBaseUrl);
58
+ url.search = query.toString();
59
+ return runProviderRequest({ signal: context.signal, label: "Framejet", timeoutMs: framejetCaptureTimeoutMs }, async (signal) => {
60
+ const response = await context.fetcher(url, { headers: framejetHeaders(context.apiKey), signal });
61
+ if (!response.ok) {
62
+ throw await readFramejetError(response);
63
+ }
64
+ const bytes = await readBoundedResponseBytes(response, {
65
+ maxBytes: transitFiles.maxBytes,
66
+ fieldName: "Framejet screenshot",
67
+ createError: (message) => new ProviderRequestError(413, message),
68
+ });
69
+ const mimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
70
+ const name = mimeType === "image/jpeg" ? "framejet-screenshot.jpg" : "framejet-screenshot.png";
71
+ const upload = await transitFiles.create(new File([Uint8Array.from(bytes)], name, { type: mimeType }));
72
+ return {
73
+ file: {
74
+ fileId: upload.fileId,
75
+ downloadUrl: upload.downloadUrl,
76
+ sizeBytes: upload.sizeBytes,
77
+ name: upload.name,
78
+ mimeType: upload.mimeType,
79
+ },
80
+ cache: response.headers.get("x-framejet-cache") === "HIT" ? "HIT" : "MISS",
81
+ remaining: optionalIntegerOrNull(response.headers.get("x-framejet-remaining")),
82
+ };
83
+ });
84
+ }
85
+ async function createFramejetSignedUrl(input, context) {
86
+ const signingKey = await runProviderRequest({ signal: context.signal, label: "Framejet" }, async (signal) => {
87
+ const response = await context.fetcher(new URL("/v1/signing-key", framejetApiBaseUrl), {
88
+ headers: framejetHeaders(context.apiKey),
89
+ signal,
90
+ });
91
+ if (!response.ok) {
92
+ throw await readFramejetError(response);
93
+ }
94
+ return requiredResponseRecord(await response.json(), "Framejet signing key response");
95
+ });
96
+ const keyId = optionalString(signingKey.key_id);
97
+ const secret = optionalString(signingKey.signing_secret);
98
+ if (!keyId || !secret) {
99
+ throw providerResponseError("Framejet signing key response is missing key_id or signing_secret");
100
+ }
101
+ const query = buildFramejetQuery(input, []);
102
+ query.set("key_id", keyId);
103
+ if (typeof input.expires_in === "number") {
104
+ query.set("expires", String(Math.floor(Date.now() / 1000) + input.expires_in));
105
+ }
106
+ // Framejet verifies the exact bytes before "&sig=", so sign the query string exactly as it is sent.
107
+ const signed = query.toString();
108
+ const sig = createHmac("sha256", secret).update(signed).digest("hex");
109
+ return { signed_url: `${framejetApiBaseUrl}/v1/take?${signed}&sig=${sig}` };
110
+ }
111
+ function buildFramejetQuery(input, extraParams) {
112
+ const query = new URLSearchParams({ url: requiredInputString(input.url, "url") });
113
+ for (const name of [...framejetCaptureParams, ...extraParams]) {
114
+ const raw = input[name];
115
+ const value = Array.isArray(raw) ? raw.join("|") : raw;
116
+ if (value === undefined || value === null || value === "") {
117
+ continue;
118
+ }
119
+ query.set(name, String(value));
120
+ }
121
+ return query;
122
+ }
123
+ function framejetHeaders(apiKey) {
124
+ return {
125
+ "user-agent": providerUserAgent,
126
+ "x-api-key": apiKey,
127
+ };
128
+ }
129
+ /**
130
+ * Map a failed Framejet response for both actions and the proxy, so a spent quota reads the same on either path.
131
+ */
132
+ export async function readFramejetError(response) {
133
+ const text = (await readProviderErrorTextBody(response, "Framejet error response")).trim();
134
+ let payload;
135
+ try {
136
+ payload = optionalRecord(JSON.parse(text));
137
+ }
138
+ catch {
139
+ payload = undefined;
140
+ }
141
+ const message = optionalString(payload?.error) ?? (text || `Framejet request failed with status ${response.status}`);
142
+ const code = optionalString(payload?.code);
143
+ const fullMessage = code ? `${message} (${code})` : message;
144
+ // 402 is Framejet's monthly quota; every other status maps through the shared convention.
145
+ return response.status === 402
146
+ ? new ProviderRequestError(402, fullMessage, payload, "insufficient_credit")
147
+ : new ProviderRequestError(response.status, fullMessage, payload);
148
+ }
@@ -0,0 +1,2 @@
1
+ import type { ActionDefinition } from "../../core/types.ts";
2
+ export declare const futunnActions: ActionDefinition[];
@@ -0,0 +1,152 @@
1
+ import { s } from "../../core/json-schema.js";
2
+ import { defineProviderAction } from "../../core/provider-definition.js";
3
+ import { futunnResearchActions } from "./actions.research.js";
4
+ import { futunnTradingActions } from "./actions.trading.js";
5
+ const symbol = s.nonEmptyString("Security code with market prefix, for example US.AAPL or HK.00700.");
6
+ const accountId = s.nonEmptyString("Trading account ID returned by list_accounts, kept as a decimal string.", {
7
+ pattern: "^[0-9]+$",
8
+ });
9
+ const date = s.string("Date in YYYY-MM-DD format in the market timezone.", { format: "date" });
10
+ const snapshot = s.looseObject("Market snapshot with additional category-specific fields.", {
11
+ code: symbol,
12
+ last_price: s.number("Latest traded price."),
13
+ update_time: s.integer("Quote update timestamp in milliseconds."),
14
+ volume: s.number("Trading volume in shares or contracts."),
15
+ });
16
+ const bar = s.looseObject("Historical candlestick with additional provider fields.", {
17
+ time_key: s.integer("Candlestick timestamp in milliseconds."),
18
+ open: s.number("Opening price."),
19
+ close: s.number("Closing price."),
20
+ high: s.number("Highest price."),
21
+ low: s.number("Lowest price."),
22
+ volume: s.number("Trading volume before applying volumePrecision."),
23
+ });
24
+ const tradingDay = s.looseObject("Trading day and session details.", {
25
+ time: date,
26
+ trade_date_type: s.string("Session type, such as WHOLE or MORNING."),
27
+ trade_second: s.integer("Total trading seconds in the session."),
28
+ });
29
+ const position = s.looseObject("Position details with additional provider fields.", {
30
+ code: symbol,
31
+ stock_name: s.string("Security name."),
32
+ qty: s.string("Position quantity as a decimal string."),
33
+ cost_price: s.string("Position cost price as a decimal string."),
34
+ cost_price_valid: s.boolean("Whether the cost price is valid."),
35
+ pl_val: s.string("Profit or loss as a decimal string."),
36
+ pl_val_valid: s.boolean("Whether the profit or loss value is valid."),
37
+ });
38
+ const quoteScope = ["quote:read"];
39
+ const tradeScope = ["trade:read"];
40
+ export const futunnActions = [
41
+ ...futunnResearchActions,
42
+ ...futunnTradingActions,
43
+ defineProviderAction("futunn", {
44
+ name: "get_market_snapshot",
45
+ operationType: "read",
46
+ description: "Get real-time market snapshots and valuation metrics for up to 400 securities.",
47
+ requiredScopes: quoteScope,
48
+ inputSchema: s.requiredObject("Securities to query.", {
49
+ symbols: s.array("Security codes to query.", symbol, { minItems: 1, maxItems: 400 }),
50
+ }),
51
+ outputSchema: s.requiredObject("Market snapshot results.", {
52
+ snapshots: s.array("Market snapshots with prices, volume, valuation and category-specific fields.", snapshot),
53
+ }),
54
+ }),
55
+ defineProviderAction("futunn", {
56
+ name: "get_history_kline",
57
+ operationType: "read",
58
+ description: "Get a page of historical candlesticks, retaining the next-page timestamp and volume precision.",
59
+ requiredScopes: quoteScope,
60
+ inputSchema: s.object("Historical candlestick query.", {
61
+ symbol,
62
+ start: date,
63
+ end: s.nonEmptyString("End date in YYYY-MM-DD format, or the previous nextTime converted to a string for pagination."),
64
+ ktype: s.withEnum(s.integer("Candlestick interval: 1=1min, 2=day, 3=week, 4=month, 5=year, 6=5min, 7=15min, 8=30min, 9=60min. Default 2."), [1, 2, 3, 4, 5, 6, 7, 8, 9]),
65
+ autype: s.withEnum(s.integer("Price adjustment: 0=none, 1=forward (default), 2=backward."), [0, 1, 2]),
66
+ num: s.integer("Number of bars requested; default and maximum 370.", {
67
+ minimum: 1,
68
+ maximum: 370,
69
+ }),
70
+ extendedTime: s.withEnum(s.integer("Extended session: 0=default, 1=US pre/after market, 2=US overnight."), [0, 1, 2]),
71
+ }, { optional: ["start", "ktype", "autype", "num", "extendedTime"] }),
72
+ outputSchema: s.requiredObject("Historical candlesticks and pagination metadata.", {
73
+ bars: s.array("Historical candlesticks in upstream order.", bar),
74
+ nextTime: s.nullable(s.integer("Next page end timestamp in milliseconds, or null when absent.")),
75
+ volumePrecision: s.nullable(s.integer("Divide bar volumes by 10 to this power; null when the provider omits precision.")),
76
+ }),
77
+ }),
78
+ defineProviderAction("futunn", {
79
+ name: "list_trading_days",
80
+ operationType: "read",
81
+ description: "List trading days for a market within an inclusive date range.",
82
+ requiredScopes: quoteScope,
83
+ inputSchema: s.requiredObject("Market and date range.", {
84
+ market: s.stringEnum("Market to query.", [
85
+ "HK",
86
+ "US",
87
+ "SH",
88
+ "SZ",
89
+ "BJ",
90
+ "SG",
91
+ "JP",
92
+ "KR",
93
+ "CA",
94
+ "AU",
95
+ "JP_FUTURE",
96
+ "SG_FUTURE",
97
+ ]),
98
+ start: date,
99
+ end: date,
100
+ }),
101
+ outputSchema: s.requiredObject("Trading calendar results.", {
102
+ tradingDays: s.array("Trading dates with session type and trading seconds.", tradingDay),
103
+ }),
104
+ }),
105
+ defineProviderAction("futunn", {
106
+ name: "list_accounts",
107
+ operationType: "read",
108
+ description: "List trading accounts authorized by the connected user.",
109
+ requiredScopes: tradeScope,
110
+ inputSchema: s.requiredObject("Authorized account query.", {}),
111
+ outputSchema: s.requiredObject("Authorized trading accounts.", {
112
+ accounts: s.array("Authorized accounts; numeric account_id values are returned as lossless decimal strings.", s.looseObject("Authorized account details.", {
113
+ account_id: s.string("Trading account ID as a lossless decimal string."),
114
+ security_firm: s.string("Broker identifier."),
115
+ acc_type: s.string("Account type, such as cash or margin."),
116
+ })),
117
+ }),
118
+ }),
119
+ defineProviderAction("futunn", {
120
+ name: "get_account_funds",
121
+ operationType: "read",
122
+ description: "Get account assets, cash, buying power and margin balances in the requested currency.",
123
+ requiredScopes: tradeScope,
124
+ inputSchema: s.requiredObject("Account funds query.", {
125
+ accountId,
126
+ currency: s.nonEmptyString("Display currency, for example USD or HKD; ignored by account types that do not support conversion."),
127
+ }),
128
+ outputSchema: s.requiredObject("Account fund balances.", {
129
+ funds: s.looseObject("Account balances; monetary amounts retain upstream decimal strings.", {
130
+ total_assets: s.string("Total net assets."),
131
+ cash: s.string("Cash balance."),
132
+ power: s.string("Maximum buying power."),
133
+ currency: s.string("Display currency."),
134
+ }),
135
+ }),
136
+ }),
137
+ defineProviderAction("futunn", {
138
+ name: "list_positions",
139
+ operationType: "read",
140
+ description: "List account positions, optionally filtered by security code and profit/loss ratio.",
141
+ requiredScopes: tradeScope,
142
+ inputSchema: s.object("Account position query.", {
143
+ accountId,
144
+ code: symbol,
145
+ plRatioMin: s.string("Minimum profit/loss percentage as a decimal string, for example 10."),
146
+ plRatioMax: s.string("Maximum profit/loss percentage as a decimal string, for example 20."),
147
+ }, { optional: ["code", "plRatioMin", "plRatioMax"] }),
148
+ outputSchema: s.requiredObject("Account position results.", {
149
+ positions: s.array("Positions with upstream quantities, prices and profit/loss decimal strings.", position),
150
+ }),
151
+ }),
152
+ ];
@@ -0,0 +1,2 @@
1
+ import type { ActionDefinition } from "../../core/types.ts";
2
+ export declare const futunnResearchActions: readonly ActionDefinition[];