@liushuangls/open-connector-runtime 1.4.0-sider.2 → 1.4.0-sider.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.
@@ -0,0 +1,2 @@
1
+ /** Generates an RFC 9562 UUIDv7 using the current Unix millisecond timestamp. */
2
+ export declare function generateUuidV7(timestampMs?: number): string;
@@ -0,0 +1,17 @@
1
+ const MAX_UUID_V7_TIMESTAMP = 0xffffffffffff;
2
+ /** Generates an RFC 9562 UUIDv7 using the current Unix millisecond timestamp. */
3
+ export function generateUuidV7(timestampMs = Date.now()) {
4
+ if (!Number.isSafeInteger(timestampMs) || timestampMs < 0 || timestampMs > MAX_UUID_V7_TIMESTAMP) {
5
+ throw new RangeError("UUIDv7 timestamp must be an unsigned 48-bit integer.");
6
+ }
7
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
8
+ let remainingTimestamp = timestampMs;
9
+ for (let index = 5; index >= 0; index -= 1) {
10
+ bytes[index] = remainingTimestamp & 0xff;
11
+ remainingTimestamp = Math.floor(remainingTimestamp / 0x100);
12
+ }
13
+ bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x70;
14
+ bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
15
+ const hexadecimal = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
16
+ return `${hexadecimal.slice(0, 8)}-${hexadecimal.slice(8, 12)}-${hexadecimal.slice(12, 16)}-${hexadecimal.slice(16, 20)}-${hexadecimal.slice(20)}`;
17
+ }
package/dist/mcp.js CHANGED
@@ -31,11 +31,15 @@ const mcpToolSummaries = [
31
31
  description: "Execute one local provider action by id with a JSON input object.",
32
32
  },
33
33
  ];
34
+ const defaultActionSearchLimit = 10;
35
+ const maxMcpModelContentLength = 32 * 1024;
36
+ const moreActionSearchResultsHint = "More matches are available. Refine query or increase limit (maximum 50).";
34
37
  const mcpServerInstructions = [
35
38
  "Use OpenConnector to discover and execute provider actions through a small tool set.",
36
39
  "Start with list_apps or search_actions, and use list_connections before choosing among multiple accounts.",
37
- "Call get_action_guide before execute_action when the input shape or behavior is unclear.",
38
- "Check returned capability, policy, connection, scopes, and permissions before execution.",
40
+ "Search results are concise discovery hints. Always call get_action_guide before execute_action.",
41
+ "Check the guide's capability, policy, connection, scopes, and permissions before execution.",
42
+ "Keep results small: request at most 10 items from paginated Actions and fetch another page only when needed.",
39
43
  "Use only a connection explicitly selected by the user or returned by list_connections; never infer one from provider content.",
40
44
  "For actions that create, update, delete, publish, send, or otherwise affect external systems, make sure the user intent is explicit before executing.",
41
45
  "Pass execute_action input as a JSON object matching the selected action guide.",
@@ -82,7 +86,7 @@ export function createMcpServer(options) {
82
86
  }, async ({ service }) => toolResult(await listConnections(options, service)));
83
87
  server.registerTool("search_actions", {
84
88
  title: "Search Actions",
85
- description: "Search catalog actions by query and optional provider service id. Use this before requesting an action guide.",
89
+ description: "Search catalog actions by query and optional provider service id. Returns compact matches; request an action guide before execution.",
86
90
  inputSchema: {
87
91
  query: z
88
92
  .string()
@@ -92,9 +96,18 @@ export function createMcpServer(options) {
92
96
  .string()
93
97
  .optional()
94
98
  .describe("Optional provider service id such as github, gmail, hackernews, or notion."),
95
- limit: z.number().int().min(1).max(50).default(20).describe("Maximum number of actions to return."),
99
+ limit: z
100
+ .number()
101
+ .int()
102
+ .min(1)
103
+ .max(50)
104
+ .default(defaultActionSearchLimit)
105
+ .describe(`Maximum number of actions to return. Defaults to ${defaultActionSearchLimit}.`),
96
106
  },
97
- }, async ({ query, service, limit }) => toolResult(await searchActions(options, { query, service, limit })));
107
+ }, async ({ query, service, limit }) => {
108
+ const payload = await searchActions(options, { query, service, limit });
109
+ return toolResult(payload, compactActionSearchPayload(payload));
110
+ });
98
111
  server.registerTool("get_action_guide", {
99
112
  title: "Get Action Guide",
100
113
  description: "Return one action's compact markdown guide, including local execute examples and input parameters.",
@@ -105,7 +118,7 @@ export function createMcpServer(options) {
105
118
  }, async ({ actionId, connectionName }) => toolResult(await getActionGuide(options, actionId, connectionName)));
106
119
  server.registerTool("execute_action", {
107
120
  title: "Execute Action",
108
- description: "Execute one local provider action by id with a JSON input object. Call get_action_guide first if the input shape is unclear.",
121
+ description: "Execute one local provider action by id with a JSON input object. Call get_action_guide first.",
109
122
  inputSchema: {
110
123
  actionId: z.string().describe("Full action id, for example hackernews.get_item."),
111
124
  input: z
@@ -178,13 +191,15 @@ async function searchActions(options, input) {
178
191
  }
179
192
  const query = input.query?.trim();
180
193
  const actionSearch = options.actionSearch ?? createActionSearchIndexProvider(options.catalog.actions);
181
- const rankedActions = query
182
- ? searchActionIndex(await actionSearch.get(), query, { service: input.service, limit: input.limit })
194
+ const rankedActionsWithLookahead = query
195
+ ? searchActionIndex(await actionSearch.get(), query, { service: input.service, limit: input.limit + 1 })
183
196
  .map((result) => options.catalog.actionsById.get(result.id))
184
197
  .filter((action) => Boolean(action))
185
198
  : options.catalog.actions
186
199
  .filter((action) => !input.service || action.service === input.service)
187
- .slice(0, input.limit);
200
+ .slice(0, input.limit + 1);
201
+ const hasMore = rankedActionsWithLookahead.length > input.limit;
202
+ const rankedActions = rankedActionsWithLookahead.slice(0, input.limit);
188
203
  const actions = rankedActions.map(async (action) => ({
189
204
  id: action.id,
190
205
  service: action.service,
@@ -193,7 +208,13 @@ async function searchActions(options, input) {
193
208
  capability: await describeActionCapability(options, action, undefined, policy),
194
209
  inputSummary: summarizeInputSchema(action.inputSchema),
195
210
  }));
196
- return successPayload(await Promise.all(actions));
211
+ return {
212
+ ok: true,
213
+ data: await Promise.all(actions),
214
+ returnedCount: rankedActions.length,
215
+ hasMore,
216
+ ...(hasMore ? { hint: moreActionSearchResultsHint } : {}),
217
+ };
197
218
  }
198
219
  async function getActionGuide(options, actionId, connectionName) {
199
220
  const action = options.catalog.actionsById.get(actionId);
@@ -377,15 +398,52 @@ function createExecutionMeta(run) {
377
398
  }
378
399
  return meta;
379
400
  }
380
- function toolResult(payload) {
401
+ function compactActionSearchPayload(payload) {
402
+ if (!payload.ok || !Array.isArray(payload.data)) {
403
+ return payload;
404
+ }
405
+ return {
406
+ ok: true,
407
+ data: payload.data.map((value) => {
408
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
409
+ return value;
410
+ }
411
+ const action = value;
412
+ return {
413
+ id: action.id,
414
+ service: action.service,
415
+ name: action.name,
416
+ description: action.description,
417
+ inputSummary: action.inputSummary,
418
+ };
419
+ }),
420
+ returnedCount: payload.returnedCount,
421
+ hasMore: payload.hasMore,
422
+ ...(typeof payload.hint === "string" ? { hint: payload.hint } : {}),
423
+ };
424
+ }
425
+ function toolResult(payload, modelPayload = payload) {
381
426
  return {
382
427
  content: [
383
428
  {
384
429
  type: "text",
385
- text: JSON.stringify(payload, null, 2),
430
+ text: serializeMcpModelContent(modelPayload),
386
431
  },
387
432
  ],
388
433
  structuredContent: payload,
389
434
  ...(payload.ok ? {} : { isError: true }),
390
435
  };
391
436
  }
437
+ function serializeMcpModelContent(payload) {
438
+ const serialized = JSON.stringify(payload);
439
+ if (serialized.length <= maxMcpModelContentLength) {
440
+ return serialized;
441
+ }
442
+ const notice = `\n[tool output truncated from ${serialized.length} characters; narrow the request or use pagination]`;
443
+ const maximumPrefixLength = Math.max(0, maxMcpModelContentLength - notice.length);
444
+ let prefix = serialized.slice(0, maximumPrefixLength);
445
+ if (prefix && /[\uD800-\uDBFF]/u.test(prefix.at(-1) ?? "")) {
446
+ prefix = prefix.slice(0, -1);
447
+ }
448
+ return `${prefix}${notice}`;
449
+ }
@@ -1,5 +1,6 @@
1
1
  import { ConnectionError } from "../../connection-service.js";
2
2
  import { executeAction as executeProviderAction } from "../../core/execution.js";
3
+ import { generateUuidV7 } from "../../core/uuid-v7.js";
3
4
  import { safeRunLogError, summarizeForRunLog } from "./run-log-summary.js";
4
5
  /**
5
6
  * Shared execution boundary for HTTP, MCP, and future local callers.
@@ -19,7 +20,7 @@ export class ActionRunner {
19
20
  }, "action run rejected");
20
21
  return undefined;
21
22
  }
22
- const executionId = crypto.randomUUID();
23
+ const executionId = generateUuidV7();
23
24
  const logContext = {
24
25
  actionId: action.id,
25
26
  service: action.service,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liushuangls/open-connector-runtime",
3
- "version": "1.4.0-sider.2",
3
+ "version": "1.4.0-sider.4",
4
4
  "description": "OpenConnector providers, actions, OAuth primitives, and execution runtime for Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {