@relvo/mcp 0.1.2 → 0.1.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.
Files changed (50) hide show
  1. package/README.md +47 -154
  2. package/bin/relvo-mcp.mjs +1 -1
  3. package/dist/client/agent-run.js +681 -0
  4. package/dist/client/browser.js +652 -0
  5. package/dist/client/evm-wallet.js +214 -0
  6. package/dist/client/index.js +3 -360
  7. package/dist/client/local-input.js +18 -11
  8. package/dist/client/local-profile.js +22 -110
  9. package/dist/client/local-setup.js +0 -1
  10. package/dist/client/tool-name-hints.js +48 -0
  11. package/dist/client/wallet-access.js +207 -0
  12. package/dist/mcp/agent-server.js +237 -0
  13. package/dist/mcp/context.js +3 -2
  14. package/dist/mcp/host-schema.js +47 -0
  15. package/dist/mcp/instructions.js +12 -2
  16. package/dist/mcp/local-upload.js +12 -4
  17. package/dist/mcp/operations.js +30 -0
  18. package/dist/mcp/runtime.js +25 -5
  19. package/dist/mcp/server.js +191 -37
  20. package/dist/mcp/version.js +1 -0
  21. package/dist/schema/accountRequestMethods.js +1 -0
  22. package/dist/schema/cronValidator.js +150 -0
  23. package/dist/schema/platformApiKey.js +5 -5
  24. package/dist/schema/platformBillingConstants.js +3 -0
  25. package/dist/schema/platformCapabilityFamilies.js +269 -0
  26. package/dist/schema/platformConnectionCatalog.js +20 -3
  27. package/dist/schema/platformConnectionPolicy.js +26 -0
  28. package/dist/schema/platformCustomTools.js +113 -0
  29. package/dist/schema/platformFunding.js +9 -0
  30. package/dist/schema/platformHttpConstraints.js +7 -0
  31. package/dist/schema/platformMcpTools.js +7 -0
  32. package/dist/schema/platformMetaTools.js +8 -6
  33. package/dist/schema/platformMonitorSchedule.js +123 -0
  34. package/dist/schema/platformMonitors.js +159 -0
  35. package/dist/schema/platformOAuth.js +7 -0
  36. package/dist/schema/platformPublicExecution.js +53 -15
  37. package/dist/schema/platformRoutes.js +61 -1
  38. package/dist/schema/platformSkills.js +1 -1
  39. package/dist/schema/platformTeamSecretName.js +3 -0
  40. package/dist/schema/platformTeamSkills.js +49 -0
  41. package/dist/schema/platformToolAccess.js +18 -0
  42. package/dist/schema/platformTriggers.js +152 -0
  43. package/dist/schema/platformWorkflows.js +100 -0
  44. package/dist/schema/scheduleTimeZone.js +15 -0
  45. package/dist/schema/toolDiscovery.js +20 -3
  46. package/dist/schema/toolInputRepair.js +110 -0
  47. package/dist/schema/toolResultContext.js +1 -1
  48. package/package.json +1 -1
  49. package/dist/schema/amazonAds.js +0 -13
  50. package/dist/schema/oauth2.js +0 -64
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { projectScopeSchema, platformJsonObjectSchema, platformMicrosSchema } from "./platformPublicExecution.js";
3
+ import { platformMonitorDestinationInputSchema, platformMonitorDestinationSchema, platformMonitorRecipientSchema, PLATFORM_MONITOR_DESTINATION_LIMITS } from "./platformMonitors.js";
4
+ import { platformWorkflowSlugSchema } from "./platformWorkflows.js";
5
+ import { PLATFORM_FREE_TOOL_OVERAGE_MICROS } from "./platformBillingConstants.js";
6
+ export const PLATFORM_TRIGGER_LIMITS = {
7
+ perProject: 50,
8
+ perTeam: 100,
9
+ nameChars: 80,
10
+ eventsPage: 100,
11
+ eventDataBytes: 65_536,
12
+ ingressBodyBytes: 1_048_576,
13
+ retentionDays: 30,
14
+ keptEvents: 10_000,
15
+ eventsPerBatch: 100,
16
+ typesPage: 100,
17
+ };
18
+ export const PLATFORM_TRIGGER_INTERVALS = { "1m": 60, "5m": 300, "15m": 900, "1h": 3_600 };
19
+ export const platformTriggerIntervalSchema = z.enum(["1m", "5m", "15m", "1h"]);
20
+ export const PLATFORM_TRIGGER_DEFAULT_MAXIMUM_CHARGE_MICROS_PER_POLL = PLATFORM_FREE_TOOL_OVERAGE_MICROS;
21
+ export const PLATFORM_TRIGGER_ID = /^trg_[A-Za-z0-9_-]{1,100}$/;
22
+ export const platformTriggerIdSchema = z.string().regex(PLATFORM_TRIGGER_ID);
23
+ export const PLATFORM_TRIGGER_TYPE = /^[a-z0-9][a-z0-9_]{0,63}\.[a-z0-9][a-z0-9_.]{0,79}$/;
24
+ export const platformTriggerTypeIdSchema = z.string().regex(PLATFORM_TRIGGER_TYPE);
25
+ const toolName = z.string().regex(/^[a-z][a-z0-9_]{1,127}$/);
26
+ const connectionId = z.string().min(1).max(128);
27
+ const pointer = z.string().max(200).regex(/^(\/[^/]*)*$/);
28
+ export const platformTriggerDestinationKindSchema = z.enum(["webhook", "slack", "discord", "email", "workflow"]);
29
+ const workflowDestination = z.object({
30
+ kind: z.literal("workflow"),
31
+ label: z.string().trim().max(PLATFORM_MONITOR_DESTINATION_LIMITS.labelChars).default(""),
32
+ workflow: platformWorkflowSlugSchema,
33
+ input: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}$/).default("event").describe("The workflow input that receives the event object."),
34
+ maximumChargeMicros: platformMicrosSchema,
35
+ }).strict();
36
+ export const platformTriggerDestinationInputSchema = z.union([platformMonitorDestinationInputSchema, workflowDestination]);
37
+ export const platformTriggerDestinationSchema = platformMonitorDestinationSchema.extend({
38
+ kind: platformTriggerDestinationKindSchema,
39
+ workflow: z.object({ slug: platformWorkflowSlugSchema, input: z.string(), maximumChargeMicros: platformMicrosSchema }).nullable().default(null),
40
+ });
41
+ const common = {
42
+ name: z.string().trim().max(PLATFORM_TRIGGER_LIMITS.nameChars).default("").describe("Empty names the trigger after its type or operation."),
43
+ connectionId: connectionId.describe("The connection of the app, from /tools/connections."),
44
+ destinations: z.array(platformTriggerDestinationInputSchema).max(PLATFORM_MONITOR_DESTINATION_LIMITS.perMonitor).default([]),
45
+ };
46
+ export const platformTriggerCreateSchema = z.discriminatedUnion("kind", [
47
+ z.object({
48
+ kind: z.literal("poll"),
49
+ tool: toolName.describe("A read operation of the connected app, for example github__issues_list_for_repo."),
50
+ arguments: platformJsonObjectSchema.default({}).describe("The operation's arguments without connectionId."),
51
+ interval: platformTriggerIntervalSchema.default("5m"),
52
+ itemsPath: pointer.optional().describe("Where the list of items is in the response body. Omit to use the largest list."),
53
+ maximumChargeMicrosPerPoll: platformMicrosSchema.default(PLATFORM_TRIGGER_DEFAULT_MAXIMUM_CHARGE_MICROS_PER_POLL),
54
+ ...common,
55
+ }).strict(),
56
+ z.object({
57
+ kind: z.literal("webhook"),
58
+ type: platformTriggerTypeIdSchema.describe("A trigger type from /trigger-types, for example github.push."),
59
+ config: platformJsonObjectSchema.default({}).describe("The type's settings, valid against its configSchema."),
60
+ ...common,
61
+ }).strict(),
62
+ ]);
63
+ export const platformTriggerUpdateSchema = z.object({
64
+ enabled: z.boolean().optional(),
65
+ name: z.string().trim().max(PLATFORM_TRIGGER_LIMITS.nameChars).optional().describe("Empty or omitted keeps the name."),
66
+ }).strict();
67
+ export const platformTriggerStatusSchema = z.enum(["active", "disabled", "failed"]);
68
+ export const platformTriggerSchema = z.object({
69
+ id: platformTriggerIdSchema,
70
+ name: z.string(),
71
+ kind: z.enum(["poll", "webhook"]),
72
+ app: z.string(),
73
+ connectionId: z.string(),
74
+ status: platformTriggerStatusSchema,
75
+ statusReason: z.string().nullable(),
76
+ tool: z.string().nullable(),
77
+ arguments: platformJsonObjectSchema.nullable(),
78
+ interval: platformTriggerIntervalSchema.nullable(),
79
+ itemsPath: z.string().nullable(),
80
+ maximumChargeMicrosPerPoll: platformMicrosSchema.nullable(),
81
+ type: z.string().nullable(),
82
+ config: platformJsonObjectSchema.nullable(),
83
+ apiKeyId: z.string(),
84
+ lastCheckedAt: z.string().nullable(),
85
+ lastEventAt: z.string().nullable(),
86
+ nextCheckAt: z.string().nullable(),
87
+ lastError: z.string().nullable(),
88
+ createdAt: z.string(),
89
+ destinations: z.array(platformTriggerDestinationSchema),
90
+ });
91
+ export const platformTriggerEventSchema = z.object({
92
+ id: z.string().regex(/^\d{1,20}$/),
93
+ triggerId: platformTriggerIdSchema,
94
+ type: z.string(),
95
+ key: z.string(),
96
+ title: z.string(),
97
+ url: z.string(),
98
+ data: platformJsonObjectSchema,
99
+ receivedAt: z.string(),
100
+ });
101
+ export const platformTriggerEventsQuerySchema = z.object({
102
+ after: z.string().regex(/^\d{1,20}$/).optional(),
103
+ limit: z.coerce.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.eventsPage).default(50),
104
+ }).strict();
105
+ export const platformTriggerEventsSchema = z.object({ events: z.array(platformTriggerEventSchema), nextAfter: z.string().nullable() });
106
+ export const platformTriggerTypeSchema = z.object({
107
+ type: platformTriggerTypeIdSchema,
108
+ app: z.string(),
109
+ name: z.string(),
110
+ description: z.string(),
111
+ configSchema: platformJsonObjectSchema,
112
+ setup: z.enum(["automatic", "app"]),
113
+ available: z.boolean(),
114
+ unavailableReason: z.string().nullable(),
115
+ });
116
+ export const platformTriggerOperationSchema = z.object({ tool: toolName, app: z.string(), description: z.string(), inputSchema: platformJsonObjectSchema });
117
+ export const platformTriggerTypesQuerySchema = z.object({
118
+ app: z.string().regex(/^[a-z0-9][a-z0-9_]{0,63}$/),
119
+ connectionId: z.string().max(128).optional(),
120
+ query: z.string().trim().max(100).default(""),
121
+ offset: z.coerce.number().int().min(0).max(100_000).default(0),
122
+ limit: z.coerce.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.typesPage).default(50),
123
+ }).strict();
124
+ export const platformTriggerTypesSchema = z.object({
125
+ types: z.array(platformTriggerTypeSchema),
126
+ operations: z.array(platformTriggerOperationSchema),
127
+ total: z.number().int().min(0),
128
+ });
129
+ export const platformTriggerDeliverySchema = z.object({
130
+ version: z.literal(1),
131
+ type: z.literal("trigger.event"),
132
+ deliveryId: z.string(),
133
+ trigger: z.object({ id: platformTriggerIdSchema, name: z.string(), kind: z.enum(["poll", "webhook"]), app: z.string(), source: z.string(), connectionId: z.string() }).strict(),
134
+ event: platformTriggerEventSchema.omit({ triggerId: true }),
135
+ }).strict();
136
+ const projectTrigger = projectScopeSchema.extend({ triggerId: platformTriggerIdSchema });
137
+ export const platformTriggerContracts = {
138
+ listDeveloperTriggers: { input: projectScopeSchema.strict(), output: z.object({ triggers: z.array(platformTriggerSchema) }).strict() },
139
+ createDeveloperTrigger: { input: projectScopeSchema.extend({ apiKeyId: z.string().min(1).max(128), trigger: platformTriggerCreateSchema }).strict(),
140
+ output: z.object({ trigger: platformTriggerSchema }).strict() },
141
+ updateDeveloperTrigger: { input: projectTrigger.merge(platformTriggerUpdateSchema).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
142
+ deleteDeveloperTrigger: { input: projectTrigger.strict(), output: z.object({ success: z.literal(true) }).strict() },
143
+ listDeveloperTriggerEvents: {
144
+ input: projectScopeSchema.extend({ triggerId: platformTriggerIdSchema.optional(), after: platformTriggerEventsQuerySchema.shape.after,
145
+ limit: z.number().int().min(1).max(PLATFORM_TRIGGER_LIMITS.eventsPage).default(50) }).strict(),
146
+ output: platformTriggerEventsSchema,
147
+ },
148
+ listDeveloperTriggerTypes: { input: projectScopeSchema.merge(platformTriggerTypesQuerySchema).strict(), output: platformTriggerTypesSchema },
149
+ addDeveloperTriggerDestination: { input: projectTrigger.extend({ destination: platformTriggerDestinationInputSchema }).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
150
+ removeDeveloperTriggerDestination: { input: projectTrigger.extend({ destinationId: platformTriggerDestinationSchema.shape.id }).strict(), output: z.object({ trigger: platformTriggerSchema }).strict() },
151
+ listDeveloperTriggerRecipients: { input: projectScopeSchema.strict(), output: z.object({ recipients: z.array(platformMonitorRecipientSchema) }).strict() },
152
+ };
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ import { platformExecutionIdSchema, platformJsonObjectSchema, platformMicrosSchema, platformProviderChoiceSchema, platformToolNameSchema } from "./platformPublicExecution.js";
3
+ import { platformCustomToolInputSchemaSchema } from "./platformCustomTools.js";
4
+ export const PLATFORM_WORKFLOW_LIMITS = {
5
+ perProject: 50,
6
+ steps: 20,
7
+ versions: 50,
8
+ activeRunsPerProject: 10,
9
+ titleChars: 80,
10
+ descriptionChars: 1_000,
11
+ stepsBytes: 32_768,
12
+ inputBytes: 16_384,
13
+ };
14
+ export const PLATFORM_WORKFLOW_SLUG = /^[a-z][a-z0-9_]{1,39}$/;
15
+ export const platformWorkflowSlugSchema = z.string().regex(PLATFORM_WORKFLOW_SLUG).describe("Lowercase letters, digits and underscores, starting with a letter.");
16
+ export const platformWorkflowStepIdSchema = z.string().regex(/^[a-z][a-z0-9_]{0,39}$/);
17
+ export const platformWorkflowRunIdSchema = z.string().regex(/^wfr_[A-Za-z0-9_-]{1,100}$/);
18
+ const pointerSchema = z.string().max(500).regex(/^(?:\/(?:[^~/]|~[01])*)*$/, "A JSON Pointer such as /data/companies/0/name");
19
+ const referenceSchema = z.union([z.object({ $input: z.string(), path: z.string().optional() }).strict(), z.object({ $step: z.string(), path: z.string().optional() }).strict()]);
20
+ export function platformWorkflowReference(value) {
21
+ const parsed = referenceSchema.safeParse(value);
22
+ if (!parsed.success)
23
+ return undefined;
24
+ const path = parsed.data.path ?? "";
25
+ return "$input" in parsed.data ? { kind: "input", name: parsed.data.$input, path } : { kind: "step", step: parsed.data.$step, path };
26
+ }
27
+ export function platformWorkflowReferences(value) {
28
+ const reference = platformWorkflowReference(value);
29
+ if (reference)
30
+ return [reference];
31
+ if (Array.isArray(value))
32
+ return value.flatMap(platformWorkflowReferences);
33
+ if (value !== null && typeof value === "object")
34
+ return Object.values(value).flatMap(platformWorkflowReferences);
35
+ return [];
36
+ }
37
+ export const platformWorkflowStepSchema = z.object({
38
+ id: platformWorkflowStepIdSchema.describe("Names the step for later steps' references: lowercase letters, digits and underscores."),
39
+ tool: platformToolNameSchema.describe("A tool of the project's catalog."),
40
+ arguments: platformJsonObjectSchema.describe("The tool's arguments. Write {\"$input\":\"name\"} for a run input or {\"$step\":\"id\",\"path\":\"/data/0/url\"} for a value of an earlier step's output."),
41
+ provider: platformProviderChoiceSchema,
42
+ }).strict();
43
+ const byteLength = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
44
+ const inputNames = (inputSchema) => {
45
+ const properties = inputSchema.properties;
46
+ return new Set(properties !== null && typeof properties === "object" && !Array.isArray(properties) ? Object.keys(properties) : []);
47
+ };
48
+ const definitionShape = {
49
+ title: z.string().trim().min(1).max(PLATFORM_WORKFLOW_LIMITS.titleChars),
50
+ description: z.string().trim().min(1).max(PLATFORM_WORKFLOW_LIMITS.descriptionChars).describe("What the workflow does and returns; agents choose workflows by it."),
51
+ inputSchema: platformCustomToolInputSchemaSchema.describe("JSON Schema of the run inputs, e.g. {\"type\":\"object\",\"properties\":{\"domain\":{\"type\":\"string\"}},\"required\":[\"domain\"]}."),
52
+ steps: z.array(platformWorkflowStepSchema).min(1).max(PLATFORM_WORKFLOW_LIMITS.steps).describe("Tool calls in the order they run; each starts after the previous one succeeded."),
53
+ };
54
+ function checkDefinition(definition, context) {
55
+ if (byteLength(definition.steps) > PLATFORM_WORKFLOW_LIMITS.stepsBytes)
56
+ context.addIssue({ code: "custom", path: ["steps"], message: "The steps are too large" });
57
+ const inputs = inputNames(definition.inputSchema), earlier = new Set();
58
+ definition.steps.forEach((step, index) => {
59
+ if (earlier.has(step.id))
60
+ context.addIssue({ code: "custom", path: ["steps", index, "id"], message: "Each step id is used once" });
61
+ for (const reference of platformWorkflowReferences(step.arguments)) {
62
+ if (!pointerSchema.safeParse(reference.path).success)
63
+ context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `Invalid path ${reference.path}` });
64
+ else if (reference.kind === "input" && !inputs.has(reference.name))
65
+ context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `No input named ${reference.name}` });
66
+ else if (reference.kind === "step" && !earlier.has(reference.step))
67
+ context.addIssue({ code: "custom", path: ["steps", index, "arguments"], message: `No earlier step named ${reference.step}` });
68
+ }
69
+ earlier.add(step.id);
70
+ });
71
+ }
72
+ export const platformWorkflowDefinitionSchema = z.object(definitionShape).strict().superRefine(checkDefinition);
73
+ export const platformWorkflowCreateSchema = z.object({ ...definitionShape, slug: platformWorkflowSlugSchema }).strict().superRefine(checkDefinition);
74
+ export const platformWorkflowUpdateSchema = z.object({ ...definitionShape, expectedVersion: z.number().int().min(1) }).strict().superRefine(checkDefinition);
75
+ export const platformWorkflowSchema = z.object({ slug: platformWorkflowSlugSchema, title: z.string(), description: z.string(), inputSchema: platformJsonObjectSchema,
76
+ steps: z.array(platformWorkflowStepSchema), version: z.number().int().min(1), createdAt: z.string(), updatedAt: z.string() });
77
+ export const platformWorkflowSummarySchema = platformWorkflowSchema.omit({ inputSchema: true, steps: true });
78
+ export const platformWorkflowVersionSchema = z.object({ version: z.number().int().min(1), title: z.string(), createdAt: z.string() });
79
+ export const platformWorkflowRunCreateSchema = z.object({
80
+ inputs: platformJsonObjectSchema.default({}).refine(value => byteLength(value) <= PLATFORM_WORKFLOW_LIMITS.inputBytes, "The inputs are too large")
81
+ .describe("Values for the workflow's input schema."),
82
+ maximumChargeMicros: platformMicrosSchema.describe("The most the whole run may charge, in micros of USD. A step whose price no longer fits stops the run before it is bought."),
83
+ version: z.number().int().min(1).optional().describe("Run an earlier kept version; omit for the current one."),
84
+ }).strict();
85
+ export const platformWorkflowRunStatusSchema = z.enum(["running", "succeeded", "failed", "needs_review", "cancelled"]);
86
+ export const platformWorkflowRunStepSchema = z.object({
87
+ id: platformWorkflowStepIdSchema, tool: platformToolNameSchema,
88
+ status: z.enum(["pending", "queued", "running", "waiting", "succeeded", "failed", "cancelled", "needs_review"]),
89
+ executionId: platformExecutionIdSchema.nullable(), chargedMicros: platformMicrosSchema, errorCode: z.string().nullable(),
90
+ }).strict();
91
+ export const platformWorkflowRunSchema = z.object({
92
+ id: platformWorkflowRunIdSchema, workflow: platformWorkflowSlugSchema, version: z.number().int().min(1), status: platformWorkflowRunStatusSchema,
93
+ cancelRequested: z.boolean(), errorCode: z.string().nullable(), inputs: platformJsonObjectSchema,
94
+ maximumChargeMicros: platformMicrosSchema, chargedMicros: platformMicrosSchema, steps: z.array(platformWorkflowRunStepSchema),
95
+ resultExecutionId: platformExecutionIdSchema.nullable(),
96
+ createdAt: z.string(), updatedAt: z.string(), completedAt: z.string().nullable(),
97
+ }).strict();
98
+ export const platformWorkflowRunSummarySchema = platformWorkflowRunSchema.omit({ inputs: true, steps: true });
99
+ export const platformWorkflowRunListQuerySchema = z.object({ before: platformWorkflowRunIdSchema.optional(), limit: z.coerce.number().int().min(1).max(50).default(20) }).strict();
100
+ export const platformWorkflowRunListSchema = z.object({ runs: z.array(platformWorkflowRunSummarySchema), nextBefore: platformWorkflowRunIdSchema.nullable() }).strict();
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export function isScheduleTimeZone(value) {
3
+ if (!/^[A-Za-z][A-Za-z0-9_+-]*(\/[A-Za-z0-9_+-]+)*$/.test(value))
4
+ return false;
5
+ try {
6
+ new Intl.DateTimeFormat("en-US", { timeZone: value });
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export const ScheduleTimeZone = z.string().max(64)
14
+ .refine(isScheduleTimeZone, "Invalid time zone (must be an IANA tz-database name).")
15
+ .transform(value => new Intl.DateTimeFormat("en-US", { timeZone: value }).resolvedOptions().timeZone);
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
- import { platformCatalogSchema, platformMicrosSchema, platformQuoteSchema, platformRequestIntentSchema, platformToolInputSchema, platformToolNameSchema } from "./platformPublicExecution.js";
2
+ import { platformCatalogSchema, platformMicrosSchema, platformQuoteSchema, platformRequestIntentSchema, platformToolInputSchema, platformToolNameSchema, validatePlatformArgumentIdentifiers } from "./platformPublicExecution.js";
3
3
  export const MAX_DISCOVERED_TOOLS = 16;
4
4
  export const SINGLE_QUERY_TOOL_LIMIT = 12;
5
5
  export const TOOL_DISCOVERY_CONTEXT_MAX_BYTES = 24_000;
6
6
  export const TOOL_DISCOVERY_REQUEST_TIMEOUT_MS = 60_000;
7
7
  export const TOOL_DISCOVERY_FILTER_MAX_VALUES = 32;
8
+ export const TOOL_DISCOVERY_APP_MAX_CHARS = 120;
8
9
  const scopeFilterSchema = z.object({
9
10
  tools: z.array(platformToolNameSchema).max(TOOL_DISCOVERY_FILTER_MAX_VALUES).default([]),
10
11
  providers: z.array(z.string().regex(/^[a-z][a-z0-9_]{1,63}$/)).max(TOOL_DISCOVERY_FILTER_MAX_VALUES).default([]),
@@ -12,7 +13,10 @@ const scopeFilterSchema = z.object({
12
13
  }).strict();
13
14
  export const toolDiscoveryInputSchema = z.object({
14
15
  intent: platformRequestIntentSchema,
15
- query: z.string().trim().max(512).transform(value => value || undefined).optional().describe("Describe a capability or exact tool/model name; empty means browse. Omit private content."),
16
+ query: z.string().trim().max(512).transform(value => value || undefined).optional()
17
+ .describe("Short English words for one capability (e.g. list open pull requests), preserving the requested action and resource, or an exact tool/model name. Put the connected service in app. Empty means browse. Omit private content."),
18
+ app: z.string().trim().max(TOOL_DISCOVERY_APP_MAX_CHARS).transform(value => value || undefined).optional()
19
+ .describe("Connected app name/ID, e.g. GitHub: search its indexed API operations only. Unknown/ambiguous or unindexed apps return explicitly unscoped results. Omit or use empty for public-data targets and known tool IDs. Discovery only; does not pin execution."),
16
20
  category: z.union([z.literal("all"), z.literal(""), platformCatalogSchema.shape.tools.element.shape.category])
17
21
  .transform(value => value === "all" || value === "" ? undefined : value).optional().describe("all or empty means any category."),
18
22
  provider: z.string().trim().regex(/^(?:[a-z][a-z0-9_]{1,63})?$/).transform(value => value || undefined).optional()
@@ -32,7 +36,13 @@ export const toolDiscoveryInputSchema = z.object({
32
36
  summary: z.boolean().default(true).describe("Summaries for comparing candidates. false requests schemas/examples; oversized definitions are omitted and remain inspectable."),
33
37
  limit: z.number().int().min(1).max(SINGLE_QUERY_TOOL_LIMIT).default(8),
34
38
  mode: z.enum(["auto", "keyword", "semantic"]).default("auto"),
35
- }).strict();
39
+ }).strict().superRefine((value, context) => {
40
+ if (value.quoteArguments == null)
41
+ return;
42
+ const named = value.include?.tools.length ? value.include.tools.length === 1 ? value.include.tools[0] : undefined
43
+ : platformToolNameSchema.safeParse(value.query).data;
44
+ validatePlatformArgumentIdentifiers(named, value.quoteArguments, context, "quoteArguments");
45
+ });
36
46
  const toolSummary = platformCatalogSchema.shape.tools.element.omit({ inputSchema: true, example: true }).extend({
37
47
  discoveryQuote: platformQuoteSchema.extend({ basis: z.enum(["arguments", "example"]) }).optional(),
38
48
  inputSchema: platformCatalogSchema.shape.tools.element.shape.inputSchema.optional(),
@@ -41,7 +51,14 @@ const toolSummary = platformCatalogSchema.shape.tools.element.omit({ inputSchema
41
51
  example: platformCatalogSchema.shape.tools.element.shape.example.optional(),
42
52
  })).optional(),
43
53
  });
54
+ export const PLATFORM_CONNECTABLE_APPS_MAX = 3;
55
+ export const platformConnectableAppsSchema = z.object({
56
+ instruction: z.string(),
57
+ apps: z.array(z.object({ app: z.string(), name: z.string(), status: z.enum(["not_connected", "reauth_required"]), connectUrl: z.url() }))
58
+ .min(1).max(PLATFORM_CONNECTABLE_APPS_MAX),
59
+ });
44
60
  export const toolDiscoveryResultSchema = platformCatalogSchema.extend({ tools: z.array(toolSummary),
45
61
  discovery: z.object({ mode: z.enum(["browse", "keyword", "semantic"]), candidateCount: z.number().int().nonnegative(), returnedCount: z.number().int().nonnegative(),
46
62
  truncated: z.boolean(), warning: z.string().optional() }),
63
+ connectableApps: platformConnectableAppsSchema.optional(),
47
64
  });
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+ const isNode = (value) => typeof value === "object" && value !== null;
3
+ const OMITTED = Symbol("omitted");
4
+ const jsonStructure = (text) => {
5
+ if (!/^\s*[[{]/.test(text))
6
+ return undefined;
7
+ try {
8
+ return JSON.parse(text);
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ };
14
+ function evidentValue(issue, sent) {
15
+ if (sent === null || sent === "")
16
+ return OMITTED;
17
+ if (issue.code === "invalid_type") {
18
+ if (typeof sent === "string" && issue.expected === "boolean")
19
+ return sent.toLowerCase() === "true" ? true : sent.toLowerCase() === "false" ? false : undefined;
20
+ if (typeof sent === "number" && issue.expected === "boolean")
21
+ return sent === 1 ? true : sent === 0 ? false : undefined;
22
+ if (typeof sent === "string" && (issue.expected === "number" || issue.expected === "int"))
23
+ return /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(sent) ? Number(sent) : undefined;
24
+ const structure = typeof sent === "string" ? jsonStructure(sent) : undefined;
25
+ if (issue.expected === "array" && Array.isArray(structure))
26
+ return structure;
27
+ if ((issue.expected === "object" || issue.expected === "record") && isNode(structure) && !Array.isArray(structure))
28
+ return structure;
29
+ if (issue.expected === "array" && structure === undefined && ["string", "number", "boolean"].includes(typeof sent) && !String(sent).includes(","))
30
+ return [sent];
31
+ if (issue.expected === "array" && isNode(sent) && !Array.isArray(sent))
32
+ return [sent];
33
+ }
34
+ if (issue.code === "invalid_format" && (issue.format === "url" || issue.format === "regex" && /^\^https\??:\/\//.test((issue.pattern ?? "").replace(/^\//, "").replaceAll("\\/", "/")))
35
+ && typeof sent === "string" && /^[a-z0-9-]+(?:\.[a-z0-9-]+)+(?::\d+)?(?:[/?#]\S*)?$/i.test(sent))
36
+ return `https://${sent}`;
37
+ if (issue.code === "invalid_value" && typeof sent === "string") {
38
+ const options = issue.values.filter(value => ["string", "number", "boolean"].includes(typeof value));
39
+ for (const form of [(text) => text.toLowerCase(), (text) => text.toLowerCase().replace(/[\s_-]/g, "")]) {
40
+ const matches = options.filter(value => form(String(value)) === form(sent));
41
+ if (matches.length)
42
+ return matches.length === 1 ? matches[0] : undefined;
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ const valueAt = (root, path) => path.reduce((node, step) => isNode(node) && typeof step !== "symbol" ? node[String(step)] : undefined, root);
48
+ function repairableIssues(issues, input) {
49
+ return issues.flatMap(issue => issue.code !== "invalid_union" ? [issue] : issue.errors
50
+ .map(option => repairableIssues(option.map(inner => ({ ...inner, path: [...issue.path, ...inner.path] })), input))
51
+ .find(option => option.length > 0 && option.every(inner => evidentValue(inner, valueAt(input, inner.path)) !== undefined)) ?? []);
52
+ }
53
+ function repaired(input, issues) {
54
+ const target = structuredClone(input), changes = [];
55
+ for (const issue of issues) {
56
+ const last = issue.path.at(-1), node = valueAt(target, issue.path.slice(0, -1));
57
+ if (last === undefined || typeof last === "symbol" || !isNode(node))
58
+ continue;
59
+ const key = String(last), sent = node[key], value = evidentValue(issue, sent);
60
+ if (value === undefined || value === OMITTED && Array.isArray(node))
61
+ continue;
62
+ if (value === OMITTED)
63
+ delete node[key];
64
+ else
65
+ node[key] = value;
66
+ changes.push({ path: issue.path, node, key, sent });
67
+ }
68
+ return { target, changes };
69
+ }
70
+ function unionOptions(schema) {
71
+ const source = schema instanceof z.ZodPipe ? schema.in : schema;
72
+ return source instanceof z.ZodUnion ? source.options.filter((option) => option instanceof z.ZodType) : [];
73
+ }
74
+ function repairPass(schema, input) {
75
+ const parsed = schema.safeParse(input);
76
+ if (parsed.success)
77
+ return input;
78
+ const { target, changes } = repaired(input, repairableIssues(parsed.error.issues, input));
79
+ const checked = schema.safeParse(target);
80
+ if (checked.success)
81
+ return target;
82
+ for (const option of unionOptions(schema)) {
83
+ const repairedOption = withEvidentValues(option, input);
84
+ if (repairedOption !== input && schema.safeParse(repairedOption).success)
85
+ return repairedOption;
86
+ }
87
+ const evident = (issue) => {
88
+ const inner = repairableIssues([issue], target);
89
+ return inner.length > 0 && inner.every(item => evidentValue(item, valueAt(target, item.path)) !== undefined);
90
+ };
91
+ const kept = changes.filter(change => {
92
+ const refused = checked.error.issues.some(issue => change.path.every((step, index) => issue.path[index] === step)
93
+ && (issue.path.length <= change.path.length || !evident(issue)));
94
+ if (refused)
95
+ change.node[change.key] = change.sent;
96
+ return !refused;
97
+ });
98
+ return kept.length ? target : input;
99
+ }
100
+ const REPAIR_PASSES = 32;
101
+ export function withEvidentValues(schema, input) {
102
+ let current = input;
103
+ for (let pass = 0; pass < REPAIR_PASSES; pass++) {
104
+ const next = repairPass(schema, current);
105
+ if (next === current)
106
+ break;
107
+ current = next;
108
+ }
109
+ return current;
110
+ }
@@ -9,7 +9,7 @@ export const toolResultSelectionSchema = z.object({
9
9
  limit: z.number().int().min(1).max(TOOL_RESULT_PAGE_MAX_ITEMS).default(20),
10
10
  maxChars: z.number().int().min(1).max(TOOL_RESULT_PAGE_MAX_CHARS).default(4_000),
11
11
  fields: z.array(z.string().max(256)).max(TOOL_RESULT_PAGE_MAX_FIELDS).optional()
12
- .describe("Top-level keys to keep from each object in an array page. Empty keeps every field. Full stored data is unchanged."),
12
+ .describe("Top-level keys to keep from one object or each object in an array page. Object offsets page the selected keys. Empty keeps every field. Full stored data is unchanged."),
13
13
  }).strict();
14
14
  export const toolResultContextSchema = z.object({
15
15
  offloaded: z.boolean(), totalBytes: z.number().int().nonnegative(), preview: z.json(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relvo/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Relvo stdio MCP server for research, SEO, social data and media generation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +0,0 @@
1
- import { z } from "zod";
2
- export const amazonAdsRegionSchema = z.enum(["na", "eu", "fe"]);
3
- export const amazonAdsProfileIdSchema = z.string().regex(/^[1-9][0-9]{0,18}$/)
4
- .pipe(z.string().refine(value => BigInt(value) <= 9223372036854775807n));
5
- export const amazonAdsProfileResourceIdSchema = z.string().regex(/^(?:na|eu|fe):[1-9][0-9]{0,18}$/)
6
- .pipe(z.string().refine(value => BigInt(value.slice(3)) <= 9223372036854775807n));
7
- export function amazonAdsProfileResourceId(region, profileId) {
8
- return amazonAdsProfileResourceIdSchema.parse(`${region}:${profileId}`);
9
- }
10
- export function parseAmazonAdsProfileResourceId(input) {
11
- const [region, profileId] = amazonAdsProfileResourceIdSchema.parse(input).split(":");
12
- return z.object({ region: amazonAdsRegionSchema, profileId: amazonAdsProfileIdSchema }).parse({ region, profileId });
13
- }
@@ -1,64 +0,0 @@
1
- import { z } from "zod";
2
- export const oauth2ProviderSchema = z.enum(["google", "x", "github", "notion", "slack", "linear", "linkedin", "pinterest", "tiktok", "microsoft", "amazon"]);
3
- export const AMAZON_OAUTH_AUTHORIZATION_URLS = { na: "https://www.amazon.com/ap/oa", eu: "https://eu.account.amazon.com/ap/oa", fe: "https://apac.account.amazon.com/ap/oa" };
4
- export const oauth2TokenResponseSchema = z.object({
5
- access_token: z.string().min(1),
6
- refresh_token: z.string().min(1).optional(),
7
- expires_in: z.number().positive().finite().optional(),
8
- scope: z.string().optional(),
9
- token_type: z.string().optional(),
10
- error: z.never().optional(),
11
- });
12
- export const oauth2ExpiringTokenResponseSchema = oauth2TokenResponseSchema.extend({
13
- expires_in: oauth2TokenResponseSchema.shape.expires_in.unwrap(),
14
- });
15
- export const oauth2ErrorResponseSchema = z.object({
16
- error: z.string().min(1), error_description: z.string().optional(), log_id: z.string().optional(),
17
- });
18
- export const OAUTH2_PROVIDERS = {
19
- google: { name: "Google", authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
20
- documentation: "https://developers.google.com/identity/protocols/oauth2/web-server" },
21
- x: { name: "X", authorizeUrl: "https://x.com/i/oauth2/authorize", tokenUrl: "https://api.x.com/2/oauth2/token", clientAuth: "basic", encoding: "form", scopeSeparator: " ", pkce: true,
22
- documentation: "https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code" },
23
- github: { name: "GitHub", authorizeUrl: "https://github.com/login/oauth/authorize", tokenUrl: "https://github.com/login/oauth/access_token", clientAuth: "body", encoding: "json", scopeSeparator: " ", pkce: true,
24
- documentation: "https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps" },
25
- notion: { name: "Notion", authorizeUrl: "https://api.notion.com/v1/oauth/authorize", tokenUrl: "https://api.notion.com/v1/oauth/token", clientAuth: "basic", encoding: "json", scopeSeparator: " ", pkce: false,
26
- documentation: "https://developers.notion.com/guides/get-started/authorization" },
27
- slack: { name: "Slack", authorizeUrl: "https://slack.com/oauth/v2/authorize", tokenUrl: "https://slack.com/api/oauth.v2.access", clientAuth: "basic", encoding: "form", scopeSeparator: ",", pkce: false,
28
- documentation: "https://docs.slack.dev/authentication/installing-with-oauth/" },
29
- linear: { name: "Linear", authorizeUrl: "https://linear.app/oauth/authorize", tokenUrl: "https://api.linear.app/oauth/token", clientAuth: "body", encoding: "form", scopeSeparator: ",", pkce: true,
30
- documentation: "https://linear.app/developers/oauth-2-0-authentication" },
31
- linkedin: { name: "LinkedIn", authorizeUrl: "https://www.linkedin.com/oauth/v2/authorization", tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: false,
32
- documentation: "https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow" },
33
- pinterest: { name: "Pinterest", authorizeUrl: "https://www.pinterest.com/oauth/", tokenUrl: "https://api.pinterest.com/v5/oauth/token", clientAuth: "basic", encoding: "form", scopeSeparator: ",", pkce: false,
34
- documentation: "https://developers.pinterest.com/docs/getting-started/set-up-authentication-and-authorization/" },
35
- tiktok: { name: "TikTok", authorizeUrl: "https://www.tiktok.com/v2/auth/authorize/", tokenUrl: "https://open.tiktokapis.com/v2/oauth/token/", clientAuth: "body", encoding: "form", scopeSeparator: ",", pkce: false,
36
- documentation: "https://developers.tiktok.com/docs/en/oauth-user-access-token-management" },
37
- microsoft: { name: "Microsoft Advertising", authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
38
- documentation: "https://learn.microsoft.com/en-us/advertising/guides/authentication-oauth-consent?view=bingads-13" },
39
- amazon: { name: "Amazon Advertising", authorizeUrl: AMAZON_OAUTH_AUTHORIZATION_URLS.na, tokenUrl: "https://api.amazon.com/auth/o2/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
40
- documentation: "https://developer.amazon.com/docs/login-with-amazon/authorization-code-grant.html" },
41
- };
42
- export const OAUTH2_TOKEN_PROTOCOLS = {
43
- ...OAUTH2_PROVIDERS,
44
- wix: { tokenUrl: "https://www.wixapis.com/oauth2/token", clientAuth: "body", encoding: "json",
45
- documentation: "https://dev.wix.com/docs/build-apps/develop-your-app/auth/authenticate-using-oauth" },
46
- wordpress: { tokenUrl: "https://public-api.wordpress.com/oauth2/token", clientAuth: "body", encoding: "form",
47
- documentation: "https://developer.wordpress.com/docs/api/oauth2/" },
48
- snapchatMarketing: { tokenUrl: "https://accounts.snapchat.com/login/oauth2/access_token", clientAuth: "body", encoding: "form",
49
- documentation: "https://developers.snap.com/marketing-api/Ads-API/authentication" },
50
- hubspot: { tokenUrl: "https://api.hubapi.com/oauth/2026-03/token", clientAuth: "body", encoding: "form",
51
- documentation: "https://developers.hubspot.com/docs/api-reference/legacy/authentication/oauth-tokens/v1/migration-guide" },
52
- attio: { tokenUrl: "https://app.attio.com/oauth/token", clientAuth: "body", encoding: "form",
53
- documentation: "https://docs.attio.com/docs/oauth/token" },
54
- pipedrive: { tokenUrl: "https://oauth.pipedrive.com/oauth/token", clientAuth: "basic", encoding: "form",
55
- documentation: "https://pipedrive.readme.io/docs/marketplace-oauth-authorization" },
56
- };
57
- export function isOAuth2AuthorizationUrl(provider, candidate) {
58
- const actual = new URL(candidate);
59
- const allowed = provider === "amazon" ? Object.values(AMAZON_OAUTH_AUTHORIZATION_URLS) : [OAUTH2_PROVIDERS[provider].authorizeUrl];
60
- return !actual.username && !actual.password && !actual.hash && allowed.some(value => {
61
- const expected = new URL(value);
62
- return actual.origin === expected.origin && actual.pathname === expected.pathname;
63
- });
64
- }