skedyul 0.1.11 → 0.1.13
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/README.md +61 -12
- package/dist/cli/commands/validate.js +2 -2
- package/dist/config.d.ts +103 -1
- package/dist/config.js +36 -0
- package/dist/index.d.ts +1 -1
- package/dist/server.js +10 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,27 +60,76 @@ For integration-specific RPCs that belong to your platform rather than a tool, p
|
|
|
60
60
|
|
|
61
61
|
The MCP server exposes `POST /core` (with `{ method, params }`) and `POST /core/webhook` for these operations. They never appear under `tools/list` unlesstaken explicit MCP tooling—they are separate transport-level handlers and do not count against tool request limits. Make sure your service returns the structured channel/message data defined in `src/core/types.ts` so the responses stay consistent, and guard `/core`/`/core/webhook` with your platform’s preferred authentication if you surface them externally.
|
|
62
62
|
|
|
63
|
-
##
|
|
63
|
+
## Core API Client
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
The SDK includes a client for the Skedyul Core API that enables lookups across workplaces. This is especially useful in webhook handlers where you need to identify which workspace a request belongs to.
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
### Configuration
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
- `workplace.get(id: string)`
|
|
71
|
-
- `communicationChannel.list(filter?: Record<string, unknown>)`
|
|
72
|
-
- `communicationChannel.get(id: string)`
|
|
69
|
+
Configure the client using environment variables or programmatically:
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
**Environment Variables:**
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Base URL for the Skedyul Core API
|
|
75
|
+
SKEDYUL_API_URL=https://app.skedyul.com/api
|
|
76
|
+
|
|
77
|
+
# Your API token (App API or Workplace API)
|
|
78
|
+
SKEDYUL_API_TOKEN=sk_app_xxxxx
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Programmatic Configuration:**
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { configure } from 'skedyul'
|
|
85
|
+
|
|
86
|
+
configure({
|
|
87
|
+
baseUrl: 'https://app.skedyul.com/api',
|
|
88
|
+
apiToken: 'sk_app_xxxxx',
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Token Types
|
|
93
|
+
|
|
94
|
+
- **App API Token (`sk_app_*`)**: Grants access to all workplaces where your app is installed. Use this for webhooks where you need to look up resources across workplaces.
|
|
95
|
+
- **Workplace API Token (`sk_wkp_*`)**: Scoped to a single workplace. Use this when you know the target workspace (e.g., MCP tools).
|
|
96
|
+
|
|
97
|
+
### Available Methods
|
|
98
|
+
|
|
99
|
+
- `workplace.list({ filter?, limit? })` - List workplaces
|
|
100
|
+
- `workplace.get(id)` - Get a single workplace
|
|
101
|
+
- `communicationChannel.list({ filter?, limit? })` - List communication channels
|
|
102
|
+
- `communicationChannel.get(id)` - Get a single channel
|
|
103
|
+
|
|
104
|
+
### Example: Webhook Handler
|
|
75
105
|
|
|
76
106
|
```ts
|
|
77
|
-
import { communicationChannel,
|
|
107
|
+
import { communicationChannel, configure } from 'skedyul'
|
|
78
108
|
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
// Configure once at startup (or use env vars)
|
|
110
|
+
configure({
|
|
111
|
+
baseUrl: process.env.SKEDYUL_API_URL,
|
|
112
|
+
apiToken: process.env.SKEDYUL_API_TOKEN,
|
|
81
113
|
})
|
|
82
114
|
|
|
83
|
-
|
|
115
|
+
// In your webhook handler
|
|
116
|
+
async function handleIncomingMessage(phoneNumber: string) {
|
|
117
|
+
// Find the channel across all workplaces where your app is installed
|
|
118
|
+
const channels = await communicationChannel.list({
|
|
119
|
+
filter: { identifierValue: phoneNumber },
|
|
120
|
+
limit: 1,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if (channels.length === 0) {
|
|
124
|
+
throw new Error('No channel found for this phone number')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const channel = channels[0]
|
|
128
|
+
console.log(`Found channel in workplace: ${channel.workplaceId}`)
|
|
129
|
+
|
|
130
|
+
// Now you can process the message in the correct workspace context
|
|
131
|
+
return channel
|
|
132
|
+
}
|
|
84
133
|
```
|
|
85
134
|
|
|
86
135
|
Use these helpers for internal wiring—like pulling the correct workplace for a webhook—without touching the MCP tooling surface directly.
|
|
@@ -163,7 +163,7 @@ async function validateCommand(args) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
// Check if workflows directory exists
|
|
166
|
-
const workflowsPath = config.
|
|
166
|
+
const workflowsPath = config.workflowsPath || './workflows';
|
|
167
167
|
const absoluteWorkflowsPath = path.resolve(path.dirname(configPath), workflowsPath);
|
|
168
168
|
if (!fs.existsSync(absoluteWorkflowsPath)) {
|
|
169
169
|
warnings.push(`Workflows directory not found: ${workflowsPath}`);
|
|
@@ -178,7 +178,7 @@ async function validateCommand(args) {
|
|
|
178
178
|
version: config.version,
|
|
179
179
|
computeLayer: config.computeLayer,
|
|
180
180
|
tools: config.tools,
|
|
181
|
-
|
|
181
|
+
workflowsPath: config.workflowsPath,
|
|
182
182
|
globalEnvKeys: envKeys.global,
|
|
183
183
|
installEnvKeys: envKeys.install,
|
|
184
184
|
requiredInstallEnvKeys: requiredInstallKeys,
|
package/dist/config.d.ts
CHANGED
|
@@ -35,6 +35,98 @@ export interface InstallConfig {
|
|
|
35
35
|
*/
|
|
36
36
|
appModels?: AppModelDefinition[];
|
|
37
37
|
}
|
|
38
|
+
export interface AppFieldVisibility {
|
|
39
|
+
/** Show in data/detail view */
|
|
40
|
+
data?: boolean;
|
|
41
|
+
/** Show in list view */
|
|
42
|
+
list?: boolean;
|
|
43
|
+
/** Show in filters */
|
|
44
|
+
filters?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface AppFieldDefinition {
|
|
47
|
+
/** Human-readable label */
|
|
48
|
+
label: string;
|
|
49
|
+
/** Field handle/key */
|
|
50
|
+
fieldHandle: string;
|
|
51
|
+
/** Entity this field belongs to (e.g., 'contact') */
|
|
52
|
+
entityHandle: string;
|
|
53
|
+
/** Metafield definition handle */
|
|
54
|
+
definitionHandle: string;
|
|
55
|
+
/** Whether this field is required */
|
|
56
|
+
required?: boolean;
|
|
57
|
+
/** Whether this is a system field */
|
|
58
|
+
system?: boolean;
|
|
59
|
+
/** Whether values must be unique */
|
|
60
|
+
unique?: boolean;
|
|
61
|
+
/** Default value */
|
|
62
|
+
defaultValue?: {
|
|
63
|
+
value: unknown;
|
|
64
|
+
};
|
|
65
|
+
/** Visibility settings */
|
|
66
|
+
visibility?: AppFieldVisibility;
|
|
67
|
+
}
|
|
68
|
+
export interface ChannelToolBindings {
|
|
69
|
+
/** Tool name for sending messages on this channel */
|
|
70
|
+
send_message: string;
|
|
71
|
+
}
|
|
72
|
+
export type ChannelIdentifierType = 'DEDICATED_PHONE' | 'TEXT' | 'EMAIL';
|
|
73
|
+
export interface ChannelIdentifierValue {
|
|
74
|
+
/** Type of identifier */
|
|
75
|
+
type: ChannelIdentifierType;
|
|
76
|
+
/** Metafield definition handle for the identifier */
|
|
77
|
+
definitionHandle: string;
|
|
78
|
+
}
|
|
79
|
+
export interface CommunicationChannelDefinition {
|
|
80
|
+
/** Unique handle for this channel type (e.g., 'sms', 'email') */
|
|
81
|
+
handle: string;
|
|
82
|
+
/** Human-readable name */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Icon for UI (lucide icon name) */
|
|
85
|
+
icon?: string;
|
|
86
|
+
/** Tool bindings for this channel */
|
|
87
|
+
tools: ChannelToolBindings;
|
|
88
|
+
/** How the channel identifier is configured */
|
|
89
|
+
identifierValue: ChannelIdentifierValue;
|
|
90
|
+
/** Fields to add to contacts when using this channel */
|
|
91
|
+
appFields?: AppFieldDefinition[];
|
|
92
|
+
/** Additional settings UI */
|
|
93
|
+
settings?: unknown[];
|
|
94
|
+
}
|
|
95
|
+
export interface WorkflowActionInput {
|
|
96
|
+
/** Input key */
|
|
97
|
+
key: string;
|
|
98
|
+
/** Human-readable label */
|
|
99
|
+
label: string;
|
|
100
|
+
/** Reference to a field */
|
|
101
|
+
fieldRef?: {
|
|
102
|
+
fieldHandle: string;
|
|
103
|
+
entityHandle: string;
|
|
104
|
+
};
|
|
105
|
+
/** Template string for the input */
|
|
106
|
+
template?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface WorkflowAction {
|
|
109
|
+
/** Human-readable label */
|
|
110
|
+
label: string;
|
|
111
|
+
/** Action handle/key */
|
|
112
|
+
handle: string;
|
|
113
|
+
/** Whether this action supports batch execution */
|
|
114
|
+
batch?: boolean;
|
|
115
|
+
/** Entity this action operates on */
|
|
116
|
+
entityHandle?: string;
|
|
117
|
+
/** Input definitions for this action */
|
|
118
|
+
inputs?: WorkflowActionInput[];
|
|
119
|
+
}
|
|
120
|
+
export interface WorkflowDefinition {
|
|
121
|
+
/** Human-readable label */
|
|
122
|
+
label: string;
|
|
123
|
+
/** Workflow handle/key */
|
|
124
|
+
handle: string;
|
|
125
|
+
/** Which channel handle this workflow is associated with (optional) */
|
|
126
|
+
channelHandle?: string;
|
|
127
|
+
/** Actions in this workflow */
|
|
128
|
+
actions: WorkflowAction[];
|
|
129
|
+
}
|
|
38
130
|
export type ComputeLayerType = 'serverless' | 'dedicated';
|
|
39
131
|
export interface SkedyulConfig {
|
|
40
132
|
/** App name */
|
|
@@ -48,7 +140,7 @@ export interface SkedyulConfig {
|
|
|
48
140
|
/** Path to the tool registry file (default: './src/registry.ts') */
|
|
49
141
|
tools?: string;
|
|
50
142
|
/** Path to the workflows directory (default: './workflows') */
|
|
51
|
-
|
|
143
|
+
workflowsPath?: string;
|
|
52
144
|
/**
|
|
53
145
|
* Global/version-level environment variables.
|
|
54
146
|
* These are baked into the container and are the same for all installations.
|
|
@@ -60,6 +152,16 @@ export interface SkedyulConfig {
|
|
|
60
152
|
* Defines what users need to configure when installing the app.
|
|
61
153
|
*/
|
|
62
154
|
install?: InstallConfig;
|
|
155
|
+
/**
|
|
156
|
+
* Communication channels this app provides.
|
|
157
|
+
* Defines how the app can send/receive messages.
|
|
158
|
+
*/
|
|
159
|
+
communicationChannels?: CommunicationChannelDefinition[];
|
|
160
|
+
/**
|
|
161
|
+
* Workflows this app provides.
|
|
162
|
+
* Can reference channels via channelHandle.
|
|
163
|
+
*/
|
|
164
|
+
workflows?: WorkflowDefinition[];
|
|
63
165
|
}
|
|
64
166
|
/**
|
|
65
167
|
* Define a Skedyul app configuration with full type safety.
|
package/dist/config.js
CHANGED
|
@@ -206,6 +206,42 @@ function validateConfig(config) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
+
// Validate communicationChannels
|
|
210
|
+
if (config.communicationChannels) {
|
|
211
|
+
for (let i = 0; i < config.communicationChannels.length; i++) {
|
|
212
|
+
const channel = config.communicationChannels[i];
|
|
213
|
+
if (!channel.handle) {
|
|
214
|
+
errors.push(`communicationChannels[${i}]: Missing required field 'handle'`);
|
|
215
|
+
}
|
|
216
|
+
if (!channel.name) {
|
|
217
|
+
errors.push(`communicationChannels[${i}]: Missing required field 'name'`);
|
|
218
|
+
}
|
|
219
|
+
if (!channel.tools?.send_message) {
|
|
220
|
+
errors.push(`communicationChannels[${i}]: Missing required field 'tools.send_message'`);
|
|
221
|
+
}
|
|
222
|
+
if (!channel.identifierValue?.type) {
|
|
223
|
+
errors.push(`communicationChannels[${i}]: Missing required field 'identifierValue.type'`);
|
|
224
|
+
}
|
|
225
|
+
if (!channel.identifierValue?.definitionHandle) {
|
|
226
|
+
errors.push(`communicationChannels[${i}]: Missing required field 'identifierValue.definitionHandle'`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Validate workflows
|
|
231
|
+
if (config.workflows) {
|
|
232
|
+
for (let i = 0; i < config.workflows.length; i++) {
|
|
233
|
+
const workflow = config.workflows[i];
|
|
234
|
+
if (!workflow.handle) {
|
|
235
|
+
errors.push(`workflows[${i}]: Missing required field 'handle'`);
|
|
236
|
+
}
|
|
237
|
+
if (!workflow.label) {
|
|
238
|
+
errors.push(`workflows[${i}]: Missing required field 'label'`);
|
|
239
|
+
}
|
|
240
|
+
if (!workflow.actions || workflow.actions.length === 0) {
|
|
241
|
+
errors.push(`workflows[${i}]: Must have at least one action`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
209
245
|
return { valid: errors.length === 0, errors };
|
|
210
246
|
}
|
|
211
247
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * from './types';
|
|
|
2
2
|
export { server } from './server';
|
|
3
3
|
export { workplace, communicationChannel } from './core/client';
|
|
4
4
|
export { defineConfig, loadConfig, validateConfig, getRequiredInstallEnvKeys, getAllEnvKeys, CONFIG_FILE_NAMES, } from './config';
|
|
5
|
-
export type { SkedyulConfig, EnvVariableDefinition, EnvSchema, EnvVisibility, InstallConfig, AppModelDefinition, ComputeLayerType, } from './config';
|
|
5
|
+
export type { SkedyulConfig, EnvVariableDefinition, EnvSchema, EnvVisibility, InstallConfig, AppModelDefinition, ComputeLayerType, AppFieldVisibility, AppFieldDefinition, ChannelToolBindings, ChannelIdentifierType, ChannelIdentifierValue, CommunicationChannelDefinition, WorkflowActionInput, WorkflowAction, WorkflowDefinition, } from './config';
|
package/dist/server.js
CHANGED
|
@@ -431,20 +431,22 @@ function createSkedyulServer(config, registry) {
|
|
|
431
431
|
const toolName = tool.name || toolKey;
|
|
432
432
|
const inputZodSchema = getZodSchema(tool.inputs);
|
|
433
433
|
const outputZodSchema = getZodSchema(tool.outputSchema);
|
|
434
|
-
|
|
434
|
+
// Wrap the input schema to accept Skedyul format: { inputs: {...}, env: {...} }
|
|
435
|
+
// This allows the MCP SDK to pass through the wrapper without stripping fields
|
|
436
|
+
const wrappedInputSchema = z.object({
|
|
437
|
+
inputs: inputZodSchema ?? z.record(z.string(), z.unknown()).optional(),
|
|
438
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
439
|
+
}).passthrough();
|
|
435
440
|
mcpServer.registerTool(toolName, {
|
|
436
441
|
title: toolName,
|
|
437
442
|
description: tool.description,
|
|
438
|
-
inputSchema:
|
|
443
|
+
inputSchema: wrappedInputSchema,
|
|
439
444
|
outputSchema: outputZodSchema,
|
|
440
445
|
}, async (args) => {
|
|
441
|
-
//
|
|
442
|
-
// 1. Skedyul format: { inputs: {...}, env: {...} }
|
|
443
|
-
// 2. Standard MCP format: { ...directArgs }
|
|
446
|
+
// Args are in Skedyul format: { inputs: {...}, env: {...} }
|
|
444
447
|
const rawArgs = args;
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const toolEnv = hasSkedyulFormat ? rawArgs.env : undefined;
|
|
448
|
+
const toolInputs = (rawArgs.inputs ?? {});
|
|
449
|
+
const toolEnv = rawArgs.env;
|
|
448
450
|
const validatedInputs = inputZodSchema ? inputZodSchema.parse(toolInputs) : toolInputs;
|
|
449
451
|
const result = await callTool(toolKey, {
|
|
450
452
|
inputs: validatedInputs,
|