@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.
- package/assets/open-connector/catalog/apps/aisa.json +6138 -312
- package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
- package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
- package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
- package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
- package/assets/open-connector/catalog/apps/framejet.json +273 -0
- package/assets/open-connector/catalog/apps/futunn.json +2967 -0
- package/assets/open-connector/catalog/apps/gmail.json +225 -0
- package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
- package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
- package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
- package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
- package/assets/open-connector/catalog/apps/googlechat.json +694 -7
- package/assets/open-connector/catalog/apps/googledocs.json +4 -1
- package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
- package/assets/open-connector/catalog/apps/granola.json +23 -0
- package/assets/open-connector/catalog/apps/keepa.json +3534 -987
- package/assets/open-connector/catalog/apps/linear.json +17 -0
- package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
- package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
- package/assets/open-connector/catalog/apps/notion.json +446 -1
- package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
- package/assets/open-connector/catalog/apps/odoo.json +619 -0
- package/assets/open-connector/catalog/apps/outlook.json +30 -8
- package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
- package/assets/open-connector/catalog/apps/postiz.json +334 -0
- package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
- package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
- package/assets/open-connector/catalog/apps/slack.json +223 -0
- package/assets/open-connector/catalog/apps/slackbot.json +214 -0
- package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
- package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
- package/assets/open-connector/catalog/apps/youtube.json +4 -1
- package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +1 -1
- package/src/core/cast.d.ts +10 -0
- package/src/core/cast.js +14 -0
- package/src/core/guarded-fetch.d.ts +6 -3
- package/src/core/guarded-fetch.js +6 -3
- package/src/core/provider-scenarios.js +67 -13
- package/src/core/request.js +4 -1
- package/src/core/types.d.ts +1 -1
- package/src/mcp.js +10 -7
- package/src/oauth/oauth-client-config-service.d.ts +16 -1
- package/src/oauth/oauth-client-config-service.js +59 -3
- package/src/oauth/oauth-flow-service.d.ts +6 -0
- package/src/oauth/oauth-flow-service.js +4 -2
- package/src/providers/17track/runtime.js +1 -1
- package/src/providers/action-contracts.generated.d.ts +17 -5
- package/src/providers/aifinmarket/runtime.js +1 -1
- package/src/providers/aisa/executors.js +734 -46
- package/src/providers/aliyun_ack/runtime.js +1 -1
- package/src/providers/aliyun_sls/runtime.js +1 -1
- package/src/providers/app_store_server/runtime-helpers.js +1 -1
- package/src/providers/baizhi_mcp/actions.d.ts +2 -0
- package/src/providers/baizhi_mcp/actions.js +40 -0
- package/src/providers/baizhi_mcp/executors.d.ts +3 -0
- package/src/providers/baizhi_mcp/executors.js +102 -0
- package/src/providers/bazhuayu/runtime.js +2 -2
- package/src/providers/bilibili/runtime.js +2 -4
- package/src/providers/bilibili/upload.js +9 -7
- package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
- package/src/providers/cloudflare_dns/executors.js +1 -1
- package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
- package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
- package/src/providers/cloudflare_dns/runtime.js +1 -1
- package/src/providers/cloudflare_r2/executors.js +1 -1
- package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
- package/src/providers/cloudflare_r2/runtime.js +1 -1
- package/src/providers/cloudflare_worker/executors.js +1 -1
- package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
- package/src/providers/cloudflare_worker/runtime.js +1 -1
- package/src/providers/dingtalk_mcp/runtime.js +1 -1
- package/src/providers/discordbot/executors.d.ts +0 -9
- package/src/providers/discordbot/executors.js +9 -101
- package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
- package/src/providers/discordbot/runtime-guilds.js +464 -0
- package/src/providers/discordbot/runtime.d.ts +42 -0
- package/src/providers/discordbot/runtime.js +124 -0
- package/src/providers/drawio_mcp/executors.d.ts +3 -0
- package/src/providers/drawio_mcp/executors.js +19 -0
- package/src/providers/drawio_mcp/runtime.d.ts +8 -0
- package/src/providers/drawio_mcp/runtime.js +175 -0
- package/src/providers/fastmoss_mcp/executors.js +2 -2
- package/src/providers/finchina/executors.js +1 -1
- package/src/providers/framejet/executors.d.ts +4 -0
- package/src/providers/framejet/executors.js +19 -0
- package/src/providers/framejet/runtime.d.ts +16 -0
- package/src/providers/framejet/runtime.js +148 -0
- package/src/providers/futunn/actions.d.ts +2 -0
- package/src/providers/futunn/actions.js +152 -0
- package/src/providers/futunn/actions.research.d.ts +2 -0
- package/src/providers/futunn/actions.research.js +423 -0
- package/src/providers/futunn/actions.trading.d.ts +2 -0
- package/src/providers/futunn/actions.trading.js +204 -0
- package/src/providers/futunn/executors.d.ts +3 -0
- package/src/providers/futunn/executors.js +24 -0
- package/src/providers/futunn/runtime-research.d.ts +3 -0
- package/src/providers/futunn/runtime-research.js +133 -0
- package/src/providers/futunn/runtime-trading.d.ts +3 -0
- package/src/providers/futunn/runtime-trading.js +39 -0
- package/src/providers/futunn/runtime.d.ts +2 -0
- package/src/providers/futunn/runtime.js +50 -0
- package/src/providers/futunn/transport.d.ts +11 -0
- package/src/providers/futunn/transport.js +76 -0
- package/src/providers/gildata/executors.js +1 -1
- package/src/providers/gmail/executors.js +3 -3
- package/src/providers/gmail/message.d.ts +6 -8
- package/src/providers/gmail/message.js +4 -0
- package/src/providers/gmail/scopes.d.ts +32 -0
- package/src/providers/gmail/scopes.js +37 -0
- package/src/providers/google_analytics/executors.js +2 -2
- package/src/providers/google_analytics/scopes.js +6 -1
- package/src/providers/google_bigquery/executors.js +2 -2
- package/src/providers/google_bigquery/scopes.js +2 -0
- package/src/providers/google_search_console/executors.js +2 -2
- package/src/providers/google_search_console/scopes.js +2 -0
- package/src/providers/googleads/executors.js +1 -1
- package/src/providers/googleads/scopes.d.ts +1 -3
- package/src/providers/googleads/scopes.js +2 -4
- package/src/providers/googlecalendar/executors.js +1 -1
- package/src/providers/googlecalendar/runtime-shared.js +2 -2
- package/src/providers/googlecalendar/scopes.js +2 -0
- package/src/providers/googlechat/executors.d.ts +1 -3
- package/src/providers/googlechat/executors.js +219 -28
- package/src/providers/googlechat/members.d.ts +55 -0
- package/src/providers/googlechat/members.js +293 -0
- package/src/providers/googlechat/runtime.d.ts +16 -0
- package/src/providers/googlechat/runtime.js +25 -0
- package/src/providers/googlechat/scopes.d.ts +22 -3
- package/src/providers/googlechat/scopes.js +30 -7
- package/src/providers/googledocs/executors.js +1 -1
- package/src/providers/googledrive/executors.js +2 -2
- package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
- package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
- package/src/providers/googledrive/runtime-collaboration.js +1 -1
- package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
- package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
- package/src/providers/googleforms/executors.js +2 -2
- package/src/providers/googleforms/scopes.d.ts +0 -3
- package/src/providers/googleforms/scopes.js +2 -6
- package/src/providers/googlemeet/executors.js +2 -2
- package/src/providers/googlemeet/scopes.d.ts +0 -3
- package/src/providers/googlemeet/scopes.js +2 -6
- package/src/providers/googlesheets/executors.js +1 -1
- package/src/providers/googlesheets/scopes.js +2 -0
- package/src/providers/googleslides/executors.js +2 -2
- package/src/providers/googleslides/scopes.d.ts +0 -3
- package/src/providers/googleslides/scopes.js +2 -6
- package/src/providers/googletasks/executors.js +2 -2
- package/src/providers/googletasks/scopes.d.ts +0 -3
- package/src/providers/googletasks/scopes.js +2 -6
- package/src/providers/granola/actions.js +2 -0
- package/src/providers/granola/runtime-mcp.js +2 -0
- package/src/providers/granola/runtime.js +2 -2
- package/src/providers/indiegogo/executors.js +1 -1
- package/src/providers/investoday_mcp/executors.js +2 -2
- package/src/providers/iyunbiao/runtime.js +2 -2
- package/src/providers/keepa/actions.d.ts +1 -1
- package/src/providers/keepa/actions.js +371 -149
- package/src/providers/keepa/deal-attributes.d.ts +1 -0
- package/src/providers/keepa/deal-attributes.js +26 -0
- package/src/providers/keepa/runtime.js +212 -18
- package/src/providers/kingdee/executors.js +1 -1
- package/src/providers/kingdee/runtime.js +2 -2
- package/src/providers/kuaidi100/runtime.js +1 -1
- package/src/providers/linear/executors.js +12 -9
- package/src/providers/linear_mcp/actions.d.ts +47 -0
- package/src/providers/linear_mcp/actions.js +102 -0
- package/src/providers/linear_mcp/endpoints.d.ts +3 -0
- package/src/providers/linear_mcp/endpoints.js +3 -0
- package/src/providers/linear_mcp/executors.d.ts +3 -0
- package/src/providers/linear_mcp/executors.js +9 -0
- package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
- package/src/providers/linear_mcp/runtime-mcp.js +238 -0
- package/src/providers/lingxing_mcp/runtime.js +1 -1
- package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
- package/src/providers/metabase/mcp-runtime.js +1 -1
- package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
- package/src/providers/moneyforward_mcp/executors.js +11 -0
- package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
- package/src/providers/moneyforward_mcp/manifest.js +204 -0
- package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
- package/src/providers/moneyforward_mcp/runtime.js +296 -0
- package/src/providers/notion/executors.js +39 -3
- package/src/providers/notion_mcp/endpoints.d.ts +3 -0
- package/src/providers/notion_mcp/endpoints.js +3 -0
- package/src/providers/notion_mcp/executors.d.ts +3 -0
- package/src/providers/notion_mcp/executors.js +176 -0
- package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
- package/src/providers/notion_mcp/mcp-response.js +314 -0
- package/src/providers/odoo/executors.d.ts +3 -0
- package/src/providers/odoo/executors.js +32 -0
- package/src/providers/odoo/runtime.d.ts +16 -0
- package/src/providers/odoo/runtime.js +140 -0
- package/src/providers/outlook/executors.js +27 -6
- package/src/providers/outlook/microsoft-graph.js +17 -6
- package/src/providers/outlook_calendar/executors.js +24 -7
- package/src/providers/patsnap_mcp/executors.js +2 -2
- package/src/providers/postiz/executors.d.ts +4 -0
- package/src/providers/postiz/executors.js +83 -0
- package/src/providers/provider-runtime.d.ts +15 -0
- package/src/providers/provider-runtime.js +57 -4
- package/src/providers/proworkflow/executors.d.ts +4 -0
- package/src/providers/proworkflow/executors.js +135 -0
- package/src/providers/registry.generated.js +12 -0
- package/src/providers/scrape_creators/catalog.js +1 -1
- package/src/providers/scrape_creators/runtime.js +1 -1
- package/src/providers/sellersprite_mcp/executors.js +2 -2
- package/src/providers/sellersprite_mcp/official-actions.js +3 -2
- package/src/providers/sentry/runtime.js +6 -7
- package/src/providers/slack/actions.js +52 -3
- package/src/providers/slack/runtime.js +58 -21
- package/src/providers/slickdeals/runtime.js +1 -1
- package/src/providers/sorftime_mcp/executors.js +2 -2
- package/src/providers/tikhub/bounded-response.d.ts +1 -1
- package/src/providers/tikhub/bounded-response.js +8 -34
- package/src/providers/tikhub/endpoint-catalog.js +1 -1
- package/src/providers/tikhub/runtime.js +2 -2
- package/src/providers/tongdaxin/executors.js +79 -15
- package/src/providers/uspto/executors.js +1 -1
- package/src/providers/waffo/executors.js +1 -1
- package/src/providers/waffo/runtime.js +1 -1
- package/src/providers/wecom_mcp/runtime.js +1 -1
- package/src/providers/wisburg/executors.d.ts +4 -0
- package/src/providers/wisburg/executors.js +13 -0
- package/src/providers/wisburg/runtime.d.ts +12 -0
- package/src/providers/wisburg/runtime.js +133 -0
- package/src/providers/xydc_mcp/executors.js +2 -2
- package/src/providers/youtube/executors.js +1 -1
- package/src/providers/youtube/runtime.js +1 -1
- package/src/providers/zerotier/executors.d.ts +9 -0
- package/src/providers/zerotier/executors.js +629 -0
- package/src/providers/zerotier/runtime.d.ts +54 -0
- package/src/providers/zerotier/runtime.js +207 -0
- package/src/server/api/openapi.js +12 -2
- package/src/server/api/runtime-api.d.ts +8 -1
- package/src/server/api/runtime-api.js +19 -1
- package/src/server/connect-server.js +10 -2
- package/src/providers/sentry/scopes.d.ts +0 -1
- package/src/providers/sentry/scopes.js +0 -7
- /package/src/providers/{cloudflare-current-user.d.ts → cloudflare_dns/runtime-user.d.ts} +0 -0
|
@@ -50,6 +50,16 @@ const messageContentProperties = {
|
|
|
50
50
|
unfurlMedia: s.boolean({ description: "Whether Slack should unfurl media in the message." }),
|
|
51
51
|
metadata: s.unknownObject("Slack message metadata to attach to the message."),
|
|
52
52
|
};
|
|
53
|
+
// Shared by `conversations.history` and `conversations.replies`: opt in to
|
|
54
|
+
// the untouched vendor record. Off by default so the row read in bulk stays
|
|
55
|
+
// lean. Opting in also asks Slack for message metadata, which it leaves out
|
|
56
|
+
// unless the request sets `include_all_metadata`.
|
|
57
|
+
const includeRawProperties = {
|
|
58
|
+
includeRaw: s.boolean({
|
|
59
|
+
description: "When true, each returned message also carries the untouched Slack record under raw, including its blocks, attachments, files and message metadata. Defaults to false.",
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
const rawMessageSchema = s.unknownObject("The untouched Slack record, exactly as the Slack API returned it. Present only when includeRaw is true.");
|
|
53
63
|
const slackReactionSchema = s.looseObject({
|
|
54
64
|
name: s.string({ description: "The emoji name of the reaction." }),
|
|
55
65
|
count: s.integer({ description: "How many users added this reaction." }),
|
|
@@ -60,9 +70,10 @@ const slackReactionSchema = s.looseObject({
|
|
|
60
70
|
// Only fields the executor actually normalizes are declared. The object is
|
|
61
71
|
// loose, but that permits extras it does NOT make them appear: the executor
|
|
62
72
|
// builds each row explicitly, so an undeclared Slack field is simply not
|
|
63
|
-
// emitted. Deliberately absent: `blocks`, `attachments
|
|
64
|
-
// are unbounded nested payloads on a row shape that ETL
|
|
65
|
-
//
|
|
73
|
+
// emitted. Deliberately absent: `blocks`, `attachments`, `files` and
|
|
74
|
+
// `metadata`. Those are unbounded nested payloads on a row shape that ETL
|
|
75
|
+
// reads in bulk, so they stay out of the default row; a caller that needs
|
|
76
|
+
// them passes includeRaw and reads them from `raw`, the untouched record.
|
|
66
77
|
const slackMessageSchema = s.looseObject({
|
|
67
78
|
ts: s.string({ description: "The message timestamp identifier." }),
|
|
68
79
|
type: s.string({ description: "The Slack message type, normally 'message'." }),
|
|
@@ -77,15 +88,20 @@ const slackMessageSchema = s.looseObject({
|
|
|
77
88
|
clientMsgId: s.string({ description: "The client-generated message identifier when Slack returns one." }),
|
|
78
89
|
text: s.string({ description: "The text content of the message." }),
|
|
79
90
|
editedTs: s.string({ description: "The timestamp of the most recent edit, when the message was edited." }),
|
|
91
|
+
editedUserId: s.string({ description: "The user ID of the most recent editor, when the message was edited." }),
|
|
80
92
|
threadTs: s.string({
|
|
81
93
|
description: "The timestamp of the thread parent. Equal to ts on a thread parent, and absent on a message that is not in a thread.",
|
|
82
94
|
}),
|
|
83
95
|
parentUserId: s.string({ description: "The author of the thread parent, on a threaded reply." }),
|
|
84
96
|
replyCount: s.integer({ description: "The number of replies to this thread parent." }),
|
|
85
97
|
replyUsersCount: s.integer({ description: "The number of distinct users who replied to this thread parent." }),
|
|
98
|
+
replyUserIds: s.array(s.string({ description: "A Slack user ID." }), {
|
|
99
|
+
description: "Up to five user IDs of people who replied to this thread parent. Slack caps this list; use replyUsersCount for the total.",
|
|
100
|
+
}),
|
|
86
101
|
latestReply: s.string({ description: "The timestamp of the most recent reply to this thread parent." }),
|
|
87
102
|
isLocked: s.boolean({ description: "Whether the thread is locked." }),
|
|
88
103
|
reactions: s.array(slackReactionSchema, { description: "Reaction summaries attached to the message." }),
|
|
104
|
+
raw: rawMessageSchema,
|
|
89
105
|
}, { description: "A Slack message record." });
|
|
90
106
|
const searchMessageMatchSchema = s.looseObject({
|
|
91
107
|
matchId: s.string({ description: "Slack's search result item identifier." }),
|
|
@@ -98,6 +114,7 @@ const searchMessageMatchSchema = s.looseObject({
|
|
|
98
114
|
permalink: s.string({ description: "A Slack permalink for the matching message." }),
|
|
99
115
|
teamId: s.string({ description: "The Slack team ID returned for the match." }),
|
|
100
116
|
type: s.string({ description: "The Slack result type." }),
|
|
117
|
+
raw: rawMessageSchema,
|
|
101
118
|
}, { description: "A normalized Slack message search match." });
|
|
102
119
|
const conversationSchema = s.object({
|
|
103
120
|
channelId: s.string({ description: "The unique identifier of the conversation." }),
|
|
@@ -113,6 +130,21 @@ const conversationSchema = s.object({
|
|
|
113
130
|
purpose: s.nullable(s.string({ description: "The conversation purpose." })),
|
|
114
131
|
userId: s.string({ description: "The linked user identifier for IM conversations." }),
|
|
115
132
|
locale: s.string({ description: "The locale returned by Slack when requested." }),
|
|
133
|
+
created: s.integer({ description: "Creation time as a Unix timestamp in seconds when Slack provides it." }),
|
|
134
|
+
updated: s.integer({
|
|
135
|
+
description: "Last settings update time, passed through as Slack returns it. Slack documents epoch milliseconds for channels (unlike created) and a Unix timestamp for legacy IM and MPIM objects.",
|
|
136
|
+
}),
|
|
137
|
+
creatorId: s.string({ description: "The user who created the conversation when Slack provides it." }),
|
|
138
|
+
isShared: s.boolean({ description: "Whether the conversation is shared with another workspace." }),
|
|
139
|
+
isExtShared: s.boolean({ description: "Whether the conversation is shared with an external organization." }),
|
|
140
|
+
isOrgShared: s.boolean({
|
|
141
|
+
description: "Whether the conversation is shared between workspaces of the same Enterprise organization.",
|
|
142
|
+
}),
|
|
143
|
+
contextTeamId: s.string({
|
|
144
|
+
description: "The ID of the workspace the conversation is within when Slack provides it.",
|
|
145
|
+
}),
|
|
146
|
+
lastRead: s.string({ description: "The last-read message timestamp when Slack provides it." }),
|
|
147
|
+
unreadCount: s.integer({ description: "The unread message count when Slack provides it." }),
|
|
116
148
|
}, {
|
|
117
149
|
required: ["channelId", "name", "type", "isArchived", "isPrivate", "isMember", "topic", "purpose"],
|
|
118
150
|
description: "A normalized Slack conversation record.",
|
|
@@ -127,6 +159,18 @@ const userSchema = s.object({
|
|
|
127
159
|
isAdmin: s.nullable(s.boolean({ description: "Whether the user is an admin." })),
|
|
128
160
|
isOwner: s.nullable(s.boolean({ description: "Whether the user is an owner." })),
|
|
129
161
|
locale: s.string({ description: "The locale returned by Slack when requested." }),
|
|
162
|
+
email: s.string({
|
|
163
|
+
description: "The profile email. Slack returns it only when the token holds the users:read.email scope.",
|
|
164
|
+
}),
|
|
165
|
+
tz: s.string({ description: "The user's time zone identifier when Slack provides it." }),
|
|
166
|
+
tzOffset: s.integer({ description: "The user's UTC offset in seconds when Slack provides it." }),
|
|
167
|
+
updated: s.integer({ description: "When the user object was last updated, as a Unix timestamp in seconds." }),
|
|
168
|
+
teamId: s.string({ description: "The user's team ID when Slack provides it." }),
|
|
169
|
+
isRestricted: s.boolean({
|
|
170
|
+
description: "Whether the user is a guest. Single-channel guests also set isUltraRestricted.",
|
|
171
|
+
}),
|
|
172
|
+
isUltraRestricted: s.boolean({ description: "Whether the user is a single-channel guest." }),
|
|
173
|
+
isAppUser: s.boolean({ description: "Whether the user is an authorized user of the calling app." }),
|
|
130
174
|
}, {
|
|
131
175
|
required: ["userId", "username", "realName", "displayName", "isBot", "isDeleted", "isAdmin", "isOwner"],
|
|
132
176
|
description: "A normalized Slack user record.",
|
|
@@ -188,6 +232,7 @@ export const slackActions = [
|
|
|
188
232
|
description: "The Slack pagination cursor from a previous page. Omit for the first page.",
|
|
189
233
|
}),
|
|
190
234
|
...historyWindowProperties,
|
|
235
|
+
...includeRawProperties,
|
|
191
236
|
}, { required: ["channelId"], description: "Input parameters for reading Slack conversation history." }),
|
|
192
237
|
outputSchema: s.object({
|
|
193
238
|
messages: s.array(slackMessageSchema, { description: "The list of messages in the conversation." }),
|
|
@@ -242,6 +287,9 @@ export const slackActions = [
|
|
|
242
287
|
sort: searchSortSchema,
|
|
243
288
|
sortDir: sortDirectionSchema,
|
|
244
289
|
teamId: s.string({ description: "The encoded team ID to search when using an org-level token." }),
|
|
290
|
+
includeRaw: s.boolean({
|
|
291
|
+
description: "When true, each returned match also carries the untouched Slack search match under raw, including any blocks, attachments and files. Defaults to false.",
|
|
292
|
+
}),
|
|
245
293
|
}, { required: ["query"], description: "Input parameters for searching Slack messages." }),
|
|
246
294
|
outputSchema: s.object({
|
|
247
295
|
query: s.string({ description: "The search query Slack executed." }),
|
|
@@ -323,6 +371,7 @@ export const slackActions = [
|
|
|
323
371
|
description: "The Slack pagination cursor from a previous page. Omit for the first page.",
|
|
324
372
|
}),
|
|
325
373
|
...historyWindowProperties,
|
|
374
|
+
...includeRawProperties,
|
|
326
375
|
}, { required: ["channelId", "threadTs"], description: "Input parameters for reading a Slack thread." }),
|
|
327
376
|
outputSchema: s.object({
|
|
328
377
|
messages: s.array(slackMessageSchema, { description: "The list of messages in the thread." }),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { compactObject, optionalBoolean, optionalInteger, optionalNumber, optionalRecord, optionalString, requiredString, } from "../../core/cast.js";
|
|
1
|
+
import { compactObject, optionalBoolean, optionalInteger, optionalNumber, optionalRecord, optionalString, optionalStringArray, requiredString, } from "../../core/cast.js";
|
|
2
2
|
import { assertPublicHttpUrl, readBoundedResponseBytes } from "../../core/request.js";
|
|
3
|
-
import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerInputError, providerResponseError, providerUserAgent, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
|
|
3
|
+
import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerInputError, providerResponseError, providerUserAgent, requiredInputString, requiredResponseRecord, runProviderRequest, withRetryAfterSeconds, } from "../provider-runtime.js";
|
|
4
4
|
import { slackConversationTypes } from "./constants.js";
|
|
5
5
|
export const slackApiBaseUrl = "https://slack.com/api";
|
|
6
6
|
const slackFileUrlMaxBytes = 100 * 1024 * 1024;
|
|
@@ -196,7 +196,8 @@ async function slackGetChannelMessages(input, context) {
|
|
|
196
196
|
url.searchParams.set("cursor", String(input.cursor));
|
|
197
197
|
}
|
|
198
198
|
applySlackHistoryWindow(url, input);
|
|
199
|
-
|
|
199
|
+
const includeRaw = applySlackIncludeRaw(url, input);
|
|
200
|
+
return readSlackMessagePage(await slackGetJson(url, context), "conversations.history", includeRaw);
|
|
200
201
|
}
|
|
201
202
|
async function slackConversationsMembers(input, context) {
|
|
202
203
|
const url = slackApiUrl("conversations.members");
|
|
@@ -214,6 +215,7 @@ async function slackConversationsMembers(input, context) {
|
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
217
|
async function slackSearchMessages(input, context) {
|
|
218
|
+
const includeRaw = input.includeRaw === true;
|
|
217
219
|
const query = requiredString(input.query, "query", (message) => new ProviderRequestError(400, message));
|
|
218
220
|
if (input.page != null && input.cursor != null) {
|
|
219
221
|
throw new ProviderRequestError(400, "page and cursor cannot be used together");
|
|
@@ -244,7 +246,7 @@ async function slackSearchMessages(input, context) {
|
|
|
244
246
|
const payload = await slackGetJson(url, context);
|
|
245
247
|
return {
|
|
246
248
|
query: optionalString(payload.query) ?? query,
|
|
247
|
-
matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match)),
|
|
249
|
+
matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match, includeRaw)),
|
|
248
250
|
total: typeof payload.messages?.total === "number" ? payload.messages.total : 0,
|
|
249
251
|
pagination: payload.messages?.pagination ?? {},
|
|
250
252
|
paging: payload.messages?.paging ?? {},
|
|
@@ -318,7 +320,8 @@ async function slackGetThread(input, context) {
|
|
|
318
320
|
url.searchParams.set("cursor", String(input.cursor));
|
|
319
321
|
}
|
|
320
322
|
applySlackHistoryWindow(url, input);
|
|
321
|
-
|
|
323
|
+
const includeRaw = applySlackIncludeRaw(url, input);
|
|
324
|
+
return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies", includeRaw);
|
|
322
325
|
}
|
|
323
326
|
async function slackListConversations(input, context) {
|
|
324
327
|
const url = slackApiUrl("conversations.list");
|
|
@@ -651,7 +654,7 @@ async function slackFormRequestJson(context, method, body) {
|
|
|
651
654
|
async function readSlackResponseJson(response) {
|
|
652
655
|
const payload = (optionalRecord(await response.json().catch(() => undefined)) ?? {});
|
|
653
656
|
if (!response.ok) {
|
|
654
|
-
throw slackHttpError(response
|
|
657
|
+
throw slackHttpError(response, payload);
|
|
655
658
|
}
|
|
656
659
|
assertSlackPayload(payload);
|
|
657
660
|
// Preserve HTTP/Slack failures (including Retry-After) above, but require
|
|
@@ -844,6 +847,15 @@ function normalizeConversation(conversation) {
|
|
|
844
847
|
purpose: typeof purpose?.value === "string" ? purpose.value : null,
|
|
845
848
|
userId: optionalString(conversation.user),
|
|
846
849
|
locale: optionalString(conversation.locale),
|
|
850
|
+
created: optionalInteger(conversation.created),
|
|
851
|
+
updated: optionalInteger(conversation.updated),
|
|
852
|
+
creatorId: optionalString(conversation.creator),
|
|
853
|
+
isShared: optionalBoolean(conversation.is_shared),
|
|
854
|
+
isExtShared: optionalBoolean(conversation.is_ext_shared),
|
|
855
|
+
isOrgShared: optionalBoolean(conversation.is_org_shared),
|
|
856
|
+
contextTeamId: optionalString(conversation.context_team_id),
|
|
857
|
+
lastRead: optionalString(conversation.last_read),
|
|
858
|
+
unreadCount: optionalInteger(conversation.unread_count),
|
|
847
859
|
});
|
|
848
860
|
}
|
|
849
861
|
/**
|
|
@@ -863,13 +875,27 @@ function applySlackHistoryWindow(url, input) {
|
|
|
863
875
|
url.searchParams.set("inclusive", String(input.inclusive));
|
|
864
876
|
}
|
|
865
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Apply the shared `includeRaw` opt-in of `conversations.history` /
|
|
880
|
+
* `conversations.replies` and report whether it is on. Only a literal `true`
|
|
881
|
+
* opts in. Slack leaves message metadata out of both methods unless the
|
|
882
|
+
* request sets `include_all_metadata`, and `raw` promises the whole record, so
|
|
883
|
+
* opting in asks for it too.
|
|
884
|
+
*/
|
|
885
|
+
function applySlackIncludeRaw(url, input) {
|
|
886
|
+
const includeRaw = input.includeRaw === true;
|
|
887
|
+
if (includeRaw) {
|
|
888
|
+
url.searchParams.set("include_all_metadata", "true");
|
|
889
|
+
}
|
|
890
|
+
return includeRaw;
|
|
891
|
+
}
|
|
866
892
|
/**
|
|
867
893
|
* Read one `conversations.history` / `conversations.replies` page. A page whose
|
|
868
894
|
* list is missing or whose rows are not objects is rejected rather than
|
|
869
895
|
* flattened into an empty result, so a broken upstream response cannot pass
|
|
870
896
|
* for the documented end of a walk.
|
|
871
897
|
*/
|
|
872
|
-
function readSlackMessagePage(payload, method) {
|
|
898
|
+
function readSlackMessagePage(payload, method, includeRaw = false) {
|
|
873
899
|
if (payload.has_more !== undefined && typeof payload.has_more !== "boolean") {
|
|
874
900
|
throw slackResponseError(`${method} has_more`);
|
|
875
901
|
}
|
|
@@ -879,7 +905,7 @@ function readSlackMessagePage(payload, method) {
|
|
|
879
905
|
if (!record) {
|
|
880
906
|
throw slackResponseError(`${method} message`);
|
|
881
907
|
}
|
|
882
|
-
return normalizeSlackMessage(record);
|
|
908
|
+
return normalizeSlackMessage(record, includeRaw);
|
|
883
909
|
}),
|
|
884
910
|
hasMore: payload.has_more ?? false,
|
|
885
911
|
nextCursor: readSlackNextCursor(payload, method),
|
|
@@ -927,8 +953,13 @@ function requireSlackId(value, label) {
|
|
|
927
953
|
* missing value stays distinguishable from an empty one. `userId` is the one
|
|
928
954
|
* exception kept for compatibility: it stays `""` on a message with no
|
|
929
955
|
* author, where `botId` / `username` carry the identity instead.
|
|
956
|
+
*
|
|
957
|
+
* `files`, `attachments`, `blocks` and `metadata` stay out of the row: they
|
|
958
|
+
* are unbounded nested payloads on a row read in bulk. With `includeRaw` the
|
|
959
|
+
* whole untouched record rides along under `raw` for a consumer that needs
|
|
960
|
+
* them, or any other field this normalizer does not model.
|
|
930
961
|
*/
|
|
931
|
-
function normalizeSlackMessage(message) {
|
|
962
|
+
function normalizeSlackMessage(message, includeRaw = false) {
|
|
932
963
|
const edited = optionalRecord(message.edited) ?? {};
|
|
933
964
|
const reactions = Array.isArray(message.reactions) ? message.reactions : undefined;
|
|
934
965
|
return compactObject({
|
|
@@ -943,13 +974,16 @@ function normalizeSlackMessage(message) {
|
|
|
943
974
|
clientMsgId: optionalString(message.client_msg_id),
|
|
944
975
|
text: typeof message.text === "string" ? message.text : "",
|
|
945
976
|
editedTs: optionalString(edited.ts),
|
|
977
|
+
editedUserId: optionalString(edited.user),
|
|
946
978
|
threadTs: optionalString(message.thread_ts),
|
|
947
979
|
parentUserId: optionalString(message.parent_user_id),
|
|
948
980
|
replyCount: optionalInteger(message.reply_count),
|
|
949
981
|
replyUsersCount: optionalInteger(message.reply_users_count),
|
|
982
|
+
replyUserIds: optionalStringArray(message.reply_users),
|
|
950
983
|
latestReply: optionalString(message.latest_reply),
|
|
951
984
|
isLocked: optionalBoolean(message.is_locked),
|
|
952
985
|
reactions: reactions?.map((reaction) => normalizeSlackReaction(optionalRecord(reaction) ?? {})),
|
|
986
|
+
raw: includeRaw ? message : undefined,
|
|
953
987
|
});
|
|
954
988
|
}
|
|
955
989
|
function normalizeSlackReaction(reaction) {
|
|
@@ -959,7 +993,7 @@ function normalizeSlackReaction(reaction) {
|
|
|
959
993
|
userIds: Array.isArray(reaction.users) ? reaction.users.map((user) => String(user)) : undefined,
|
|
960
994
|
});
|
|
961
995
|
}
|
|
962
|
-
function normalizeSearchMessageMatch(match) {
|
|
996
|
+
function normalizeSearchMessageMatch(match, includeRaw = false) {
|
|
963
997
|
const channel = optionalRecord(match.channel) ?? {};
|
|
964
998
|
return compactObject({
|
|
965
999
|
matchId: optionalString(match.iid),
|
|
@@ -972,6 +1006,7 @@ function normalizeSearchMessageMatch(match) {
|
|
|
972
1006
|
permalink: optionalString(match.permalink),
|
|
973
1007
|
teamId: optionalString(match.team),
|
|
974
1008
|
type: optionalString(match.type),
|
|
1009
|
+
raw: includeRaw ? match : undefined,
|
|
975
1010
|
});
|
|
976
1011
|
}
|
|
977
1012
|
function normalizeUser(user) {
|
|
@@ -986,6 +1021,14 @@ function normalizeUser(user) {
|
|
|
986
1021
|
isAdmin: typeof user.is_admin === "boolean" ? user.is_admin : null,
|
|
987
1022
|
isOwner: typeof user.is_owner === "boolean" ? user.is_owner : null,
|
|
988
1023
|
locale: optionalString(user.locale),
|
|
1024
|
+
email: optionalString(profile.email),
|
|
1025
|
+
tz: optionalString(user.tz),
|
|
1026
|
+
tzOffset: optionalInteger(user.tz_offset),
|
|
1027
|
+
updated: optionalInteger(user.updated),
|
|
1028
|
+
teamId: optionalString(user.team_id),
|
|
1029
|
+
isRestricted: optionalBoolean(user.is_restricted),
|
|
1030
|
+
isUltraRestricted: optionalBoolean(user.is_ultra_restricted),
|
|
1031
|
+
isAppUser: optionalBoolean(user.is_app_user),
|
|
989
1032
|
});
|
|
990
1033
|
}
|
|
991
1034
|
function normalizeFile(file) {
|
|
@@ -1037,17 +1080,11 @@ function formatSlackPayloadError(payload) {
|
|
|
1037
1080
|
}
|
|
1038
1081
|
return `${error}: ${details.join("; ")}`;
|
|
1039
1082
|
}
|
|
1040
|
-
function slackHttpError(
|
|
1041
|
-
const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${status}`;
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
// The action envelope carries provider details; retain pacing so callers
|
|
1046
|
-
// can resume the same page without guessing when this workspace may retry.
|
|
1047
|
-
return new ProviderRequestError(status, message, { ...payload, retryAfterSeconds });
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
return new ProviderRequestError(status, message, payload);
|
|
1083
|
+
function slackHttpError(response, payload) {
|
|
1084
|
+
const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${response.status}`;
|
|
1085
|
+
// The action envelope carries provider details; retain pacing so callers
|
|
1086
|
+
// can resume the same page without guessing when this workspace may retry.
|
|
1087
|
+
return new ProviderRequestError(response.status, message, withRetryAfterSeconds(response, payload));
|
|
1051
1088
|
}
|
|
1052
1089
|
function slackResponseError(message) {
|
|
1053
1090
|
return new ProviderRequestError(502, `slack response is invalid: ${message}`);
|
|
@@ -45,7 +45,7 @@ export const proxy = defineProviderProxy({
|
|
|
45
45
|
baseUrl: sorftimeMcpEndpoint,
|
|
46
46
|
auth: { type: "api_key_authorization", prefix: "Bearer " },
|
|
47
47
|
skipDnsValidation: true,
|
|
48
|
-
redirect: "
|
|
48
|
+
redirect: "manual",
|
|
49
49
|
timeoutMs: requestTimeoutMs,
|
|
50
50
|
allowedEndpoint(endpoint) {
|
|
51
51
|
const url = new URL(endpoint, sorftimeMcpEndpoint);
|
|
@@ -79,7 +79,7 @@ function withSorftimeClient(context, run) {
|
|
|
79
79
|
authorization: `Bearer ${context.apiKey}`,
|
|
80
80
|
"user-agent": providerUserAgent,
|
|
81
81
|
},
|
|
82
|
-
redirect: "
|
|
82
|
+
redirect: "manual",
|
|
83
83
|
signal: context.signal,
|
|
84
84
|
mapError: mapSorftimeError,
|
|
85
85
|
}, run);
|
|
@@ -5,4 +5,4 @@ export declare function readBoundedResponseText(response: Response, input: {
|
|
|
5
5
|
maxBytes: number;
|
|
6
6
|
label: string;
|
|
7
7
|
}): Promise<string>;
|
|
8
|
-
export declare function cancelResponseBody(response: Response):
|
|
8
|
+
export declare function cancelResponseBody(response: Response): void;
|
|
@@ -1,44 +1,18 @@
|
|
|
1
|
+
import { readBoundedResponseBytes } from "../../core/request.js";
|
|
1
2
|
import { TikHubRequestError } from "./errors.js";
|
|
2
3
|
export class BoundedResponseTooLargeError extends TikHubRequestError {
|
|
3
4
|
}
|
|
4
5
|
export async function readBoundedResponseText(response, input) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
throw responseTooLarge(input);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
if (!response.body) {
|
|
14
|
-
return "";
|
|
15
|
-
}
|
|
16
|
-
const reader = response.body.getReader();
|
|
17
|
-
const chunks = [];
|
|
18
|
-
let totalBytes = 0;
|
|
19
|
-
while (true) {
|
|
20
|
-
const result = await reader.read();
|
|
21
|
-
if (result.done) {
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
totalBytes += result.value.byteLength;
|
|
25
|
-
if (totalBytes > input.maxBytes) {
|
|
26
|
-
await reader.cancel();
|
|
27
|
-
throw responseTooLarge(input);
|
|
28
|
-
}
|
|
29
|
-
chunks.push(result.value);
|
|
30
|
-
}
|
|
31
|
-
const bytes = new Uint8Array(totalBytes);
|
|
32
|
-
let offset = 0;
|
|
33
|
-
for (const chunk of chunks) {
|
|
34
|
-
bytes.set(chunk, offset);
|
|
35
|
-
offset += chunk.byteLength;
|
|
36
|
-
}
|
|
6
|
+
const bytes = await readBoundedResponseBytes(response, {
|
|
7
|
+
maxBytes: input.maxBytes,
|
|
8
|
+
fieldName: input.label,
|
|
9
|
+
createError: () => responseTooLarge(input),
|
|
10
|
+
});
|
|
37
11
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
38
12
|
}
|
|
39
|
-
export
|
|
13
|
+
export function cancelResponseBody(response) {
|
|
40
14
|
try {
|
|
41
|
-
|
|
15
|
+
void response.body?.cancel().catch(() => undefined);
|
|
42
16
|
}
|
|
43
17
|
catch {
|
|
44
18
|
return;
|
|
@@ -105,7 +105,7 @@ async function fetchOpenApi(fetcher) {
|
|
|
105
105
|
const response = await fetcher(tikhubOpenApiUrl, {
|
|
106
106
|
method: "GET",
|
|
107
107
|
headers: { accept: "application/json" },
|
|
108
|
-
redirect: "
|
|
108
|
+
redirect: "manual",
|
|
109
109
|
signal: timeout.signal,
|
|
110
110
|
});
|
|
111
111
|
if (!response.ok) {
|
|
@@ -166,7 +166,7 @@ async function invokeTikHubEndpoint(input, fetcher, apiKey) {
|
|
|
166
166
|
"user-agent": providerUserAgent,
|
|
167
167
|
},
|
|
168
168
|
...(serializedBody === undefined ? {} : { body: serializedBody }),
|
|
169
|
-
redirect: "
|
|
169
|
+
redirect: "manual",
|
|
170
170
|
signal: timeout.signal,
|
|
171
171
|
});
|
|
172
172
|
let responseBody;
|
|
@@ -454,7 +454,7 @@ async function requestTikHubUserJson(input) {
|
|
|
454
454
|
authorization: `Bearer ${input.apiKey}`,
|
|
455
455
|
"user-agent": providerUserAgent,
|
|
456
456
|
},
|
|
457
|
-
redirect: "
|
|
457
|
+
redirect: "manual",
|
|
458
458
|
signal: timeout.signal,
|
|
459
459
|
});
|
|
460
460
|
const payload = await readTikHubResponseBody(response, tikhubDynamicResponseMaxBytes);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProtocolError, SdkHttpError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { optionalRecord } from "../../core/cast.js";
|
|
3
|
+
import { optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
|
|
4
|
+
import { readBoundedResponseBytes } from "../../core/request.js";
|
|
4
5
|
import { withMcpClient } from "../mcp-client.js";
|
|
5
6
|
import { defineApiKeyProviderExecutors, providerUserAgent, ProviderRequestError, requiredInputString, } from "../provider-runtime.js";
|
|
6
7
|
import { tongdaxinActions, tongdaxinReadOnlyToolNames } from "./actions.js";
|
|
@@ -9,10 +10,15 @@ import { isTongdaxinNamedActionName, resolveTongdaxinNamedToolCall } from "./nam
|
|
|
9
10
|
const service = "tongdaxin";
|
|
10
11
|
const endpoint = new URL("https://txmcp.tdx.com.cn:3001/txmcp");
|
|
11
12
|
const requestTimeoutMs = 60_000;
|
|
13
|
+
const errorResponseMaxBytes = 64 * 1024;
|
|
14
|
+
const insufficientPointsToolError = "MCP error -32603: Your usage quota has been reached";
|
|
15
|
+
const insufficientPointsDescription = "TDX MCP quota is below 10. Please recharge before continuing.";
|
|
16
|
+
const rechargeOrigin = "https://vip.tdx.com.cn";
|
|
17
|
+
const rechargePath = "/site/app/pc-mall/main.html";
|
|
12
18
|
const supportedToolNames = new Set(tongdaxinReadOnlyToolNames);
|
|
13
19
|
const handlers = {
|
|
14
20
|
async list_tools(_input, context) {
|
|
15
|
-
return { tools: await discoverSupportedTools(context
|
|
21
|
+
return { tools: await discoverSupportedTools(context) };
|
|
16
22
|
},
|
|
17
23
|
async call_tool(input, context) {
|
|
18
24
|
const toolName = requiredInputString(input.toolName, "toolName");
|
|
@@ -20,7 +26,8 @@ const handlers = {
|
|
|
20
26
|
if (!argumentsValue)
|
|
21
27
|
throw new ProviderRequestError(400, "arguments must be a JSON object");
|
|
22
28
|
assertToolArgumentsSize(argumentsValue);
|
|
23
|
-
const result = await withTongdaxinClient(context,
|
|
29
|
+
const result = await withTongdaxinClient(context, false, (client) => client.callTool({ name: toolName, arguments: argumentsValue }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
30
|
+
assertSufficientTongdaxinCredit(result);
|
|
24
31
|
if (!("toolResult" in result) && result.isError) {
|
|
25
32
|
throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolName} returned an error`, result);
|
|
26
33
|
}
|
|
@@ -37,7 +44,7 @@ for (const actionName of Object.keys(resolveNamedHandlers())) {
|
|
|
37
44
|
throw new ProviderRequestError(400, `Unknown action: ${actionName}`);
|
|
38
45
|
const toolCall = resolveTongdaxinNamedToolCall(actionName, input);
|
|
39
46
|
assertToolArgumentsSize(toolCall.arguments);
|
|
40
|
-
const tools = await discoverSupportedTools(context
|
|
47
|
+
const tools = await discoverSupportedTools(context);
|
|
41
48
|
const tool = tools.find((candidate) => candidate.name === toolCall.toolName);
|
|
42
49
|
if (!tool && toolCall.toolName !== "wenda_macro_query") {
|
|
43
50
|
throw new ProviderRequestError(409, `Tongdaxin action ${actionName} is unavailable because this connection does not expose ${toolCall.toolName}`);
|
|
@@ -48,7 +55,8 @@ for (const actionName of Object.keys(resolveNamedHandlers())) {
|
|
|
48
55
|
tool.annotations.destructiveHint === true)) {
|
|
49
56
|
throw new ProviderRequestError(403, `Tongdaxin MCP does not currently affirm ${tool.name} as a non-destructive read-only tool`);
|
|
50
57
|
}
|
|
51
|
-
const result = await withTongdaxinClient(context,
|
|
58
|
+
const result = await withTongdaxinClient(context, true, (client) => client.callTool({ name: toolCall.toolName, arguments: toolCall.arguments }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
59
|
+
assertSufficientTongdaxinCredit(result);
|
|
52
60
|
if (!("toolResult" in result) && result.isError) {
|
|
53
61
|
throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolCall.toolName} returned an error`, result);
|
|
54
62
|
}
|
|
@@ -62,7 +70,7 @@ export const executors = defineApiKeyProviderExecutors(service, handlers, {
|
|
|
62
70
|
export const credentialValidators = {
|
|
63
71
|
async apiKey(input, { fetcher, signal }) {
|
|
64
72
|
const context = { apiKey: input.apiKey, fetcher, signal };
|
|
65
|
-
const tools = await discoverSupportedTools(context
|
|
73
|
+
const tools = await discoverSupportedTools(context);
|
|
66
74
|
if (tools.length === 0) {
|
|
67
75
|
throw new ProviderRequestError(400, "Tongdaxin MCP did not expose any supported read-only tools");
|
|
68
76
|
}
|
|
@@ -77,8 +85,8 @@ export const credentialValidators = {
|
|
|
77
85
|
};
|
|
78
86
|
},
|
|
79
87
|
};
|
|
80
|
-
async function discoverSupportedTools(context
|
|
81
|
-
const result = await withTongdaxinClient(context,
|
|
88
|
+
async function discoverSupportedTools(context) {
|
|
89
|
+
const result = await withTongdaxinClient(context, true, (client) => client.listTools({}, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
82
90
|
return result.tools.map((tool) => ({
|
|
83
91
|
name: tool.name,
|
|
84
92
|
description: tool.description,
|
|
@@ -86,30 +94,30 @@ async function discoverSupportedTools(context, phase) {
|
|
|
86
94
|
inputSchema: tool.inputSchema,
|
|
87
95
|
}));
|
|
88
96
|
}
|
|
89
|
-
async function withTongdaxinClient(context,
|
|
97
|
+
async function withTongdaxinClient(context, retryOnSessionNotFound, run) {
|
|
90
98
|
return withMcpClient({
|
|
91
99
|
endpoint,
|
|
92
100
|
transport: "streamable_http",
|
|
93
|
-
fetcher: context.fetcher,
|
|
101
|
+
fetcher: createTongdaxinFetcher(context.fetcher),
|
|
94
102
|
headers: {
|
|
95
103
|
authorization: `Bearer ${context.apiKey}`,
|
|
96
104
|
"user-agent": providerUserAgent,
|
|
97
105
|
},
|
|
98
|
-
redirect: "
|
|
106
|
+
redirect: "manual",
|
|
99
107
|
signal: context.signal,
|
|
100
108
|
retryOnSessionNotFound,
|
|
101
|
-
mapError:
|
|
109
|
+
mapError: mapTongdaxinError,
|
|
102
110
|
}, run);
|
|
103
111
|
}
|
|
104
|
-
function mapTongdaxinError(error
|
|
112
|
+
function mapTongdaxinError(error) {
|
|
105
113
|
if (error instanceof ProviderRequestError)
|
|
106
114
|
return error;
|
|
107
115
|
if (error instanceof UnauthorizedError) {
|
|
108
|
-
return new ProviderRequestError(
|
|
116
|
+
return new ProviderRequestError(401, "Tongdaxin MCP denied this request");
|
|
109
117
|
}
|
|
110
118
|
if (error instanceof SdkHttpError) {
|
|
111
119
|
if (error.status === 401) {
|
|
112
|
-
return new ProviderRequestError(
|
|
120
|
+
return new ProviderRequestError(401, "Tongdaxin MCP denied this request");
|
|
113
121
|
}
|
|
114
122
|
const status = 400 <= error.status && error.status < 500 ? error.status : 502;
|
|
115
123
|
return new ProviderRequestError(status, `Tongdaxin MCP request failed: ${error.message}`, error);
|
|
@@ -119,6 +127,62 @@ function mapTongdaxinError(error, phase) {
|
|
|
119
127
|
}
|
|
120
128
|
return new ProviderRequestError(502, error instanceof Error ? `Tongdaxin MCP request failed: ${error.message}` : "Tongdaxin MCP request failed", error);
|
|
121
129
|
}
|
|
130
|
+
function createTongdaxinFetcher(fetcher) {
|
|
131
|
+
return (async (...arguments_) => {
|
|
132
|
+
const response = await fetcher(...arguments_);
|
|
133
|
+
const details = await readInsufficientPoints(response);
|
|
134
|
+
if (details) {
|
|
135
|
+
void response.body?.cancel().catch(() => undefined);
|
|
136
|
+
throw insufficientCreditError(details);
|
|
137
|
+
}
|
|
138
|
+
return response;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function readInsufficientPoints(response) {
|
|
142
|
+
if (response.status !== 401)
|
|
143
|
+
return undefined;
|
|
144
|
+
try {
|
|
145
|
+
const bytes = await readBoundedResponseBytes(response.clone(), {
|
|
146
|
+
maxBytes: errorResponseMaxBytes,
|
|
147
|
+
fieldName: "Tongdaxin MCP error response",
|
|
148
|
+
createError: (message) => new Error(message),
|
|
149
|
+
});
|
|
150
|
+
const payload = optionalRecord(JSON.parse(new TextDecoder().decode(bytes)));
|
|
151
|
+
const remainingCredit = optionalNumber(payload?.leftValue);
|
|
152
|
+
const rechargeUrl = optionalString(payload?.recharge_url);
|
|
153
|
+
if (payload?.error !== "unauthorized" ||
|
|
154
|
+
payload?.error_description !== insufficientPointsDescription ||
|
|
155
|
+
remainingCredit === undefined ||
|
|
156
|
+
!isOfficialRechargeUrl(rechargeUrl)) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
return { remainingCredit, rechargeUrl };
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function isOfficialRechargeUrl(value) {
|
|
166
|
+
if (!value)
|
|
167
|
+
return false;
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(value);
|
|
170
|
+
return url.origin === rechargeOrigin && url.pathname === rechargePath;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function assertSufficientTongdaxinCredit(result) {
|
|
177
|
+
if (!("content" in result) || result.isError !== true)
|
|
178
|
+
return;
|
|
179
|
+
const exhausted = result.content.some((content) => content.type === "text" && content.text === insufficientPointsToolError);
|
|
180
|
+
if (exhausted)
|
|
181
|
+
throw insufficientCreditError();
|
|
182
|
+
}
|
|
183
|
+
function insufficientCreditError(details) {
|
|
184
|
+
return new ProviderRequestError(402, "Tongdaxin AI points are insufficient. Recharge the account before retrying.", details ?? { rechargeUrl: `${rechargeOrigin}${rechargePath}#/page_product_ai_jfb` }, "insufficient_credit");
|
|
185
|
+
}
|
|
122
186
|
function resolveNamedHandlers() {
|
|
123
187
|
return Object.fromEntries(tongdaxinActions
|
|
124
188
|
.filter((action) => action.name !== "list_tools" && action.name !== "call_tool")
|
|
@@ -2,7 +2,7 @@ import { defineApiKeyProviderExecutors, defineProviderProxy, ProviderRequestErro
|
|
|
2
2
|
async function get(path, accept, apiKey, fetcher, signal) {
|
|
3
3
|
const response = await fetcher(new URL(path, "https://tsdrapi.uspto.gov"), {
|
|
4
4
|
headers: { accept, "USPTO-API-KEY": apiKey, "user-agent": providerUserAgent },
|
|
5
|
-
redirect: "
|
|
5
|
+
redirect: "manual",
|
|
6
6
|
signal,
|
|
7
7
|
});
|
|
8
8
|
const text = await response.text();
|
|
@@ -37,7 +37,7 @@ export const proxy = async (input, context) => {
|
|
|
37
37
|
method: input.method,
|
|
38
38
|
headers,
|
|
39
39
|
body: input.method === "GET" || input.body == null ? undefined : signingBody,
|
|
40
|
-
redirect: "
|
|
40
|
+
redirect: "manual",
|
|
41
41
|
signal: context.signal,
|
|
42
42
|
});
|
|
43
43
|
if (!response.ok) {
|
|
@@ -173,7 +173,7 @@ async function withWeComMcpClient(context, run) {
|
|
|
173
173
|
transport: "streamable_http",
|
|
174
174
|
fetcher: createWeComRateLimitedFetch(context.fetcher, hashWeComRateLimitKey(context)),
|
|
175
175
|
headers,
|
|
176
|
-
redirect: "
|
|
176
|
+
redirect: "manual",
|
|
177
177
|
signal: context.signal,
|
|
178
178
|
mapError: mapWeComMcpError,
|
|
179
179
|
}, run);
|