@oomol-lab/open-connector 1.6.3 → 1.6.4
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/feishu_app_bot.json +2 -2
- package/assets/open-connector/catalog/apps/jin10.json +1 -1
- package/assets/open-connector/catalog/apps/tongdaxin.json +11 -440
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +1 -1
- package/src/providers/dropbox/executors.js +21 -3
- package/src/providers/feishu_app_bot/scopes.js +1 -1
- package/src/providers/mcp-client.d.ts +1 -0
- package/src/providers/mcp-client.js +41 -27
- package/src/providers/sellersprite_mcp/executors.js +2 -1
- package/src/providers/slack/runtime.js +2 -2
- package/src/providers/tongdaxin/executors.js +5 -4
- package/src/providers/tongdaxin/named-action-output.js +3 -9
- package/src/providers/tongdaxin/named-action-routing.js +1 -1
- package/src/providers/tongdaxin/named-actions.js +1 -6
- package/src/providers/twenty_crm/runtime.js +4 -2
package/package.json
CHANGED
|
@@ -209,6 +209,24 @@ async function getMetadata(input, accessToken, fetcher) {
|
|
|
209
209
|
metadata: mapDropboxMetadata(payload),
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Serialize a `Dropbox-API-Arg` header value.
|
|
214
|
+
*
|
|
215
|
+
* Dropbox requires this header to be ASCII: its own docs say `0x7F` and every
|
|
216
|
+
* non-ASCII character must be escaped as `\uXXXX`, which is also the range the
|
|
217
|
+
* official JS SDK escapes. `JSON.stringify` does not escape, and Node's `Headers`
|
|
218
|
+
* refuses a value outside ByteString, so a path containing any non-ASCII
|
|
219
|
+
* character threw `TypeError: Cannot convert argument to a ByteString` inside
|
|
220
|
+
* the runtime BEFORE the request was made. `provider-runtime.ts`'s fallback
|
|
221
|
+
* then reported it as a bare `internal_error`, discarding the cause.
|
|
222
|
+
*
|
|
223
|
+
* Measured 2026-09-18 against a real account: `/kkndme 天涯.pdf` failed in 1ms
|
|
224
|
+
* with no network call, while ASCII-path siblings downloaded in ~400ms. Dropbox
|
|
225
|
+
* itself accepts either spelling on the wire; only the header build was broken.
|
|
226
|
+
*/
|
|
227
|
+
function dropboxApiArg(arg) {
|
|
228
|
+
return JSON.stringify(arg).replace(/[\u007f-\uffff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
229
|
+
}
|
|
212
230
|
async function downloadFile(input, context) {
|
|
213
231
|
const { accessToken, fetcher, transitFiles } = context;
|
|
214
232
|
if (!transitFiles) {
|
|
@@ -218,7 +236,7 @@ async function downloadFile(input, context) {
|
|
|
218
236
|
method: "POST",
|
|
219
237
|
headers: {
|
|
220
238
|
...dropboxAuthHeaders(accessToken),
|
|
221
|
-
"Dropbox-API-Arg":
|
|
239
|
+
"Dropbox-API-Arg": dropboxApiArg({
|
|
222
240
|
path: requireString(input.path, "dropbox download path"),
|
|
223
241
|
}),
|
|
224
242
|
},
|
|
@@ -251,7 +269,7 @@ async function uploadFile(input, accessToken, fetcher) {
|
|
|
251
269
|
headers: {
|
|
252
270
|
...dropboxAuthHeaders(accessToken),
|
|
253
271
|
"Content-Type": source.mimeType || "application/octet-stream",
|
|
254
|
-
"Dropbox-API-Arg":
|
|
272
|
+
"Dropbox-API-Arg": dropboxApiArg(arg),
|
|
255
273
|
},
|
|
256
274
|
body: Buffer.from(source.bytes),
|
|
257
275
|
});
|
|
@@ -491,7 +509,7 @@ async function getSharedLinkFile(input, context) {
|
|
|
491
509
|
method: "POST",
|
|
492
510
|
headers: {
|
|
493
511
|
...dropboxAuthHeaders(accessToken),
|
|
494
|
-
"Dropbox-API-Arg":
|
|
512
|
+
"Dropbox-API-Arg": dropboxApiArg(arg),
|
|
495
513
|
},
|
|
496
514
|
signal: context.signal,
|
|
497
515
|
});
|
|
@@ -15,6 +15,6 @@ export const feishuAppBotScopes = {
|
|
|
15
15
|
groupAtReadonly: "im:message.group_at_msg:readonly",
|
|
16
16
|
groupAtWithBotReadonly: "im:message.group_at_msg.include_bot:readonly",
|
|
17
17
|
groupReadonly: "im:message.group_msg:readonly",
|
|
18
|
-
applicationRead: "application:application:
|
|
18
|
+
applicationRead: "application:application:self_manage",
|
|
19
19
|
applicationVersionRead: "application:application.app_version:readonly",
|
|
20
20
|
};
|
|
@@ -10,6 +10,7 @@ export interface McpClientOptions {
|
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
protocolVersion?: McpProtocolVersion;
|
|
12
12
|
terminateSession?: boolean;
|
|
13
|
+
retryOnSessionNotFound?: boolean;
|
|
13
14
|
mapError?: (error: unknown) => unknown;
|
|
14
15
|
}
|
|
15
16
|
export declare function withMcpClient<T>(options: McpClientOptions, run: (client: Client) => Promise<T>): Promise<T>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client } from "@modelcontextprotocol/client";
|
|
1
|
+
import { Client, SdkHttpError } from "@modelcontextprotocol/client";
|
|
2
2
|
import { SSEClientTransport } from "@modelcontextprotocol/client";
|
|
3
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
4
4
|
import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/client/validators/cf-worker";
|
|
@@ -6,33 +6,47 @@ const mcpConnectTimeoutMs = 60_000;
|
|
|
6
6
|
const modernMcpProtocolVersion = "2026-07-28";
|
|
7
7
|
const mcpJsonSchemaValidator = new CfWorkerJsonSchemaValidator();
|
|
8
8
|
export async function withMcpClient(options, run) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
9
|
+
let retriedSession = false;
|
|
10
|
+
while (true) {
|
|
11
|
+
const transportOptions = {
|
|
12
|
+
fetch: options.fetcher,
|
|
13
|
+
requestInit: {
|
|
14
|
+
headers: options.headers,
|
|
15
|
+
redirect: options.redirect,
|
|
16
|
+
signal: options.signal,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const transport = options.transport === "sse"
|
|
20
|
+
? new SSEClientTransport(options.endpoint, transportOptions)
|
|
21
|
+
: new StreamableHTTPClientTransport(options.endpoint, transportOptions);
|
|
22
|
+
const client = new Client({ name: "open-connector", version: "1.0.0" }, {
|
|
23
|
+
jsonSchemaValidator: mcpJsonSchemaValidator,
|
|
24
|
+
versionNegotiation: { mode: resolveVersionNegotiationMode(options.protocolVersion) },
|
|
25
|
+
});
|
|
26
|
+
let connected = false;
|
|
27
|
+
try {
|
|
28
|
+
await client.connect(transport, { timeout: mcpConnectTimeoutMs, signal: options.signal });
|
|
29
|
+
connected = true;
|
|
30
|
+
return await run(client);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (connected &&
|
|
34
|
+
options.transport === "streamable_http" &&
|
|
35
|
+
options.retryOnSessionNotFound === true &&
|
|
36
|
+
!retriedSession &&
|
|
37
|
+
error instanceof SdkHttpError &&
|
|
38
|
+
error.status === 404) {
|
|
39
|
+
retriedSession = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
throw options.mapError ? options.mapError(error) : error;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
if (options.terminateSession && transport instanceof StreamableHTTPClientTransport) {
|
|
46
|
+
await terminateMcpSession(transport);
|
|
47
|
+
}
|
|
48
|
+
await client.close().catch(() => undefined);
|
|
34
49
|
}
|
|
35
|
-
await client.close().catch(() => undefined);
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
async function terminateMcpSession(transport) {
|
|
@@ -87,8 +87,9 @@ function normalize(result) {
|
|
|
87
87
|
const message = envelope.message ?? `SellerSprite MCP tool failed with ${envelope.code}`;
|
|
88
88
|
if (["ERROR_UNAUTHORIZED", "ERROR_SECRET_KEY", "ERROR_SECRET_KEY_INVALID", "ERROR_SECRET_KEY_OVERDUE"].includes(envelope.code))
|
|
89
89
|
throw new ProviderRequestError(401, message, envelope);
|
|
90
|
-
if (envelope.code === "ERROR_VISIT_MAX")
|
|
90
|
+
if (envelope.code === "ERROR_VISIT_MAX" || envelope.code === "ERROR_MAXIMUM_ACCESS_PER_MINUTE") {
|
|
91
91
|
throw new ProviderRequestError(429, message, envelope);
|
|
92
|
+
}
|
|
92
93
|
if (envelope.code === "ERROR_AUTH_ERROR")
|
|
93
94
|
throw new ProviderRequestError(403, message, envelope);
|
|
94
95
|
if (envelope.code === "ERROR_PARAM")
|
|
@@ -688,10 +688,10 @@ function readSlackTokenKind(accessToken, metadata) {
|
|
|
688
688
|
if (rawTokenType == "user") {
|
|
689
689
|
return "user";
|
|
690
690
|
}
|
|
691
|
-
if (accessToken.startsWith("xoxb-")) {
|
|
691
|
+
if (accessToken.startsWith("xoxb-") || accessToken.startsWith("xoxe.xoxb-")) {
|
|
692
692
|
return "bot";
|
|
693
693
|
}
|
|
694
|
-
if (accessToken.startsWith("xoxp-")) {
|
|
694
|
+
if (accessToken.startsWith("xoxp-") || accessToken.startsWith("xoxe.xoxp-")) {
|
|
695
695
|
return "user";
|
|
696
696
|
}
|
|
697
697
|
return undefined;
|
|
@@ -20,7 +20,7 @@ const handlers = {
|
|
|
20
20
|
if (!argumentsValue)
|
|
21
21
|
throw new ProviderRequestError(400, "arguments must be a JSON object");
|
|
22
22
|
assertToolArgumentsSize(argumentsValue);
|
|
23
|
-
const result = await withTongdaxinClient(context, "execute", (client) => client.callTool({ name: toolName, arguments: argumentsValue }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
23
|
+
const result = await withTongdaxinClient(context, "execute", false, (client) => client.callTool({ name: toolName, arguments: argumentsValue }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
24
24
|
if (!("toolResult" in result) && result.isError) {
|
|
25
25
|
throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolName} returned an error`, result);
|
|
26
26
|
}
|
|
@@ -48,7 +48,7 @@ for (const actionName of Object.keys(resolveNamedHandlers())) {
|
|
|
48
48
|
tool.annotations.destructiveHint === true)) {
|
|
49
49
|
throw new ProviderRequestError(403, `Tongdaxin MCP does not currently affirm ${tool.name} as a non-destructive read-only tool`);
|
|
50
50
|
}
|
|
51
|
-
const result = await withTongdaxinClient(context, "execute", (client) => client.callTool({ name: toolCall.toolName, arguments: toolCall.arguments }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
51
|
+
const result = await withTongdaxinClient(context, "execute", true, (client) => client.callTool({ name: toolCall.toolName, arguments: toolCall.arguments }, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
52
52
|
if (!("toolResult" in result) && result.isError) {
|
|
53
53
|
throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolCall.toolName} returned an error`, result);
|
|
54
54
|
}
|
|
@@ -78,7 +78,7 @@ export const credentialValidators = {
|
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
80
|
async function discoverSupportedTools(context, phase) {
|
|
81
|
-
const result = await withTongdaxinClient(context, phase, (client) => client.listTools({}, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
81
|
+
const result = await withTongdaxinClient(context, phase, true, (client) => client.listTools({}, { timeout: requestTimeoutMs, signal: context.signal }));
|
|
82
82
|
return result.tools.map((tool) => ({
|
|
83
83
|
name: tool.name,
|
|
84
84
|
description: tool.description,
|
|
@@ -86,7 +86,7 @@ async function discoverSupportedTools(context, phase) {
|
|
|
86
86
|
inputSchema: tool.inputSchema,
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
|
-
async function withTongdaxinClient(context, phase, run) {
|
|
89
|
+
async function withTongdaxinClient(context, phase, retryOnSessionNotFound, run) {
|
|
90
90
|
return withMcpClient({
|
|
91
91
|
endpoint,
|
|
92
92
|
transport: "streamable_http",
|
|
@@ -97,6 +97,7 @@ async function withTongdaxinClient(context, phase, run) {
|
|
|
97
97
|
},
|
|
98
98
|
redirect: "error",
|
|
99
99
|
signal: context.signal,
|
|
100
|
+
retryOnSessionNotFound,
|
|
100
101
|
mapError: (error) => mapTongdaxinError(error, phase),
|
|
101
102
|
}, run);
|
|
102
103
|
}
|
|
@@ -40,7 +40,7 @@ export function normalizeTongdaxinNamedActionOutput(actionName, result) {
|
|
|
40
40
|
case "get_board_and_industry_data":
|
|
41
41
|
case "get_hot_topics":
|
|
42
42
|
case "get_hk_financials":
|
|
43
|
-
return normalizeF10(resultObject
|
|
43
|
+
return normalizeF10(resultObject);
|
|
44
44
|
default:
|
|
45
45
|
return assertNever(actionName);
|
|
46
46
|
}
|
|
@@ -103,15 +103,9 @@ function normalizeSearch(resultObject, result) {
|
|
|
103
103
|
result,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
-
function normalizeF10(resultObject
|
|
106
|
+
function normalizeF10(resultObject) {
|
|
107
107
|
const response = requiredResponseRecord(resultObject.response, "Tongdaxin F10 response");
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
ok: optionalBoolean(resultObject.ok) ?? optionalBoolean(response.ok) ?? null,
|
|
111
|
-
summary: asStringOrNull(transformed.summary ?? resultObject.summary),
|
|
112
|
-
tables: requireResponseObjectArray(transformed.tables, "Tongdaxin F10 tables"),
|
|
113
|
-
result,
|
|
114
|
-
};
|
|
108
|
+
return response.transformed ?? null;
|
|
115
109
|
}
|
|
116
110
|
function assertNever(value) {
|
|
117
111
|
throw providerResponseError(`unhandled Tongdaxin action output: ${value}`);
|
|
@@ -295,7 +295,7 @@ function capitalAndTradingArguments(input) {
|
|
|
295
295
|
extra: input.date,
|
|
296
296
|
};
|
|
297
297
|
case "BLOCK_TRADE_INTENTION":
|
|
298
|
-
return dataSection("TdxSharePCCW.
|
|
298
|
+
return dataSection("TdxSharePCCW.tdxf10_gg_jyds", code, "yxsbxx");
|
|
299
299
|
case "MARGIN_TRADING":
|
|
300
300
|
return dataSection("TdxSharePCCW.tdxf10_gg_jyds", code, "rzrq");
|
|
301
301
|
case "REFINANCING":
|
|
@@ -62,12 +62,7 @@ const searchOutputSchema = s.object("A normalized Tongdaxin current-information
|
|
|
62
62
|
data: s.nullable(s.unknown("The provider-defined search rows returned by Wenda, or null when unavailable.")),
|
|
63
63
|
result: rawResultSchema,
|
|
64
64
|
});
|
|
65
|
-
const f10OutputSchema = s.
|
|
66
|
-
ok: s.nullableBoolean("Whether the underlying F10 request reported success."),
|
|
67
|
-
summary: s.nullableString("The structured result summary, or null when unavailable."),
|
|
68
|
-
tables: s.array("The formatted F10 result tables.", looseRowSchema),
|
|
69
|
-
result: rawResultSchema,
|
|
70
|
-
});
|
|
65
|
+
const f10OutputSchema = s.unknown("The transformed F10 data returned by Tongdaxin, preserved as any JSON value.");
|
|
71
66
|
function defineReadAction(name, description, inputSchema, outputSchema = f10OutputSchema) {
|
|
72
67
|
return defineProviderAction(service, {
|
|
73
68
|
name,
|
|
@@ -57,9 +57,10 @@ async function listMetadataObjects(context, phase = "execute") {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
async function listRecords(input, context) {
|
|
60
|
+
const objectNamePlural = readRequiredString(input.objectNamePlural, "objectNamePlural");
|
|
60
61
|
const payload = await requestTwentyCrmJson({
|
|
61
62
|
method: "GET",
|
|
62
|
-
path: `/rest/${encodeObjectName(
|
|
63
|
+
path: `/rest/${encodeObjectName(objectNamePlural)}`,
|
|
63
64
|
query: compactObject({
|
|
64
65
|
limit: optionalInteger(input.limit),
|
|
65
66
|
starting_after: readOptionalString(input.startingAfter),
|
|
@@ -70,8 +71,9 @@ async function listRecords(input, context) {
|
|
|
70
71
|
}),
|
|
71
72
|
}, context);
|
|
72
73
|
const record = requireProviderObject(payload, "Twenty CRM list records response");
|
|
74
|
+
const data = requireProviderObject(record.data, "Twenty CRM data");
|
|
73
75
|
return {
|
|
74
|
-
records: readArray(
|
|
76
|
+
records: readArray(data, objectNamePlural).map((item) => requireProviderObject(item, "Twenty CRM record")),
|
|
75
77
|
pageInfo: optionalRecord(record.pageInfo) ?? {},
|
|
76
78
|
raw: record,
|
|
77
79
|
};
|