skedyul 0.1.0 → 0.1.1

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/dist/server.js CHANGED
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.server = void 0;
7
7
  exports.createSkedyulServer = createSkedyulServer;
8
8
  const http_1 = __importDefault(require("http"));
9
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
10
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
9
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
10
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
11
11
  const service_1 = require("./core/service");
12
12
  function normalizeBilling(billing) {
13
13
  if (!billing || typeof billing.credits !== 'number') {
@@ -181,9 +181,9 @@ async function handleCoreMethod(method, params) {
181
181
  };
182
182
  }
183
183
  function buildToolMetadata(registry) {
184
- return Object.keys(registry).map((name) => ({
185
- name,
186
- description: `Function: ${name}`,
184
+ return Object.values(registry).map((tool) => ({
185
+ name: tool.name,
186
+ description: tool.description,
187
187
  inputSchema: {
188
188
  type: 'object',
189
189
  properties: {
@@ -224,13 +224,14 @@ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNam
224
224
  function createCallToolHandler(registry, state, onMaxRequests) {
225
225
  return async function callTool(nameRaw, argsRaw) {
226
226
  const toolName = String(nameRaw);
227
- const fn = registry[toolName];
228
- if (!fn) {
227
+ const tool = registry[toolName];
228
+ if (!tool) {
229
229
  throw new Error(`Tool "${toolName}" not found in registry`);
230
230
  }
231
- if (typeof fn !== 'function') {
232
- throw new Error(`Registry entry "${toolName}" is not a function`);
231
+ if (!tool.handler || typeof tool.handler !== 'function') {
232
+ throw new Error(`Tool "${toolName}" handler is not a function`);
233
233
  }
234
+ const fn = tool.handler;
234
235
  const args = (argsRaw ?? {});
235
236
  const estimateMode = args.estimate === true;
236
237
  if (!estimateMode) {
@@ -333,7 +334,7 @@ function createSkedyulServer(config, registry) {
333
334
  }
334
335
  }
335
336
  const tools = buildToolMetadata(registry);
336
- const toolNames = tools.map((tool) => tool.name);
337
+ const toolNames = Object.values(registry).map((tool) => tool.name);
337
338
  const runtimeLabel = config.computeLayer;
338
339
  const maxRequests = config.maxRequests ??
339
340
  parseNumberEnv(process.env.MCP_MAX_REQUESTS) ??
@@ -342,13 +343,9 @@ function createSkedyulServer(config, registry) {
342
343
  parseNumberEnv(process.env.MCP_TTL_EXTEND) ??
343
344
  3600;
344
345
  const state = createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNames);
345
- const server = new index_js_1.Server({
346
+ const mcpServer = new mcp_js_1.McpServer({
346
347
  name: config.metadata.name,
347
348
  version: config.metadata.version,
348
- }, {
349
- capabilities: {
350
- tools: {},
351
- },
352
349
  });
353
350
  const dedicatedShutdown = () => {
354
351
  // eslint-disable-next-line no-console
@@ -356,16 +353,34 @@ function createSkedyulServer(config, registry) {
356
353
  setTimeout(() => process.exit(0), 1000);
357
354
  };
358
355
  const callTool = createCallToolHandler(registry, state, config.computeLayer === 'dedicated' ? dedicatedShutdown : undefined);
359
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
360
- tools,
361
- }));
362
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => callTool(request.params.name, request.params.arguments));
356
+ // Register all tools from the registry
357
+ for (const [toolKey, tool] of Object.entries(registry)) {
358
+ // Use the tool's name or fall back to the registry key
359
+ const toolName = tool.name || toolKey;
360
+ mcpServer.registerTool(toolName, {
361
+ title: toolName,
362
+ description: tool.description,
363
+ inputSchema: tool.inputs,
364
+ outputSchema: tool.outputSchema,
365
+ }, async (args) => {
366
+ // Args will be the parsed Zod schema values directly
367
+ const result = await callTool(toolKey, {
368
+ inputs: args,
369
+ });
370
+ return {
371
+ content: result.content,
372
+ structuredContent: result.isError
373
+ ? undefined
374
+ : JSON.parse(result.content[0]?.text ?? '{}'),
375
+ };
376
+ });
377
+ }
363
378
  if (config.computeLayer === 'dedicated') {
364
- return createDedicatedServerInstance(config, tools, callTool, state);
379
+ return createDedicatedServerInstance(config, tools, callTool, state, mcpServer);
365
380
  }
366
- return createServerlessInstance(config, tools, callTool, state);
381
+ return createServerlessInstance(config, tools, callTool, state, mcpServer, registry);
367
382
  }
368
- function createDedicatedServerInstance(config, tools, callTool, state) {
383
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer) {
369
384
  const port = getListeningPort(config);
370
385
  const httpServer = http_1.default.createServer(async (req, res) => {
371
386
  function sendCoreResult(result) {
@@ -465,62 +480,22 @@ function createDedicatedServerInstance(config, tools, callTool, state) {
465
480
  return;
466
481
  }
467
482
  if (pathname === '/mcp' && req.method === 'POST') {
468
- let body;
469
- try {
470
- body = (await parseJSONBody(req));
471
- }
472
- catch {
473
- sendJSON(res, 400, {
474
- jsonrpc: '2.0',
475
- id: null,
476
- error: {
477
- code: -32700,
478
- message: 'Parse error',
479
- },
480
- });
481
- return;
482
- }
483
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
484
+ sessionIdGenerator: undefined,
485
+ enableJsonResponse: true,
486
+ });
487
+ res.on('close', () => {
488
+ transport.close();
489
+ });
483
490
  try {
484
- const { jsonrpc, id, method, params } = body;
485
- if (jsonrpc !== '2.0') {
486
- sendJSON(res, 400, {
487
- jsonrpc: '2.0',
488
- id,
489
- error: {
490
- code: -32600,
491
- message: 'Invalid Request',
492
- },
493
- });
494
- return;
495
- }
496
- let result;
497
- if (method === 'tools/list') {
498
- result = { tools };
499
- }
500
- else if (method === 'tools/call') {
501
- result = await callTool(params?.name, params?.arguments);
502
- }
503
- else {
504
- sendJSON(res, 200, {
505
- jsonrpc: '2.0',
506
- id,
507
- error: {
508
- code: -32601,
509
- message: `Method not found: ${method}`,
510
- },
511
- });
512
- return;
513
- }
514
- sendJSON(res, 200, {
515
- jsonrpc: '2.0',
516
- id,
517
- result,
518
- });
491
+ const body = await parseJSONBody(req);
492
+ await mcpServer.connect(transport);
493
+ await transport.handleRequest(req, res, body);
519
494
  }
520
495
  catch (err) {
521
496
  sendJSON(res, 500, {
522
497
  jsonrpc: '2.0',
523
- id: body?.id ?? null,
498
+ id: null,
524
499
  error: {
525
500
  code: -32603,
526
501
  message: err instanceof Error ? err.message : String(err ?? ''),
@@ -568,7 +543,7 @@ function createDedicatedServerInstance(config, tools, callTool, state) {
568
543
  getHealthStatus: () => state.getHealthStatus(),
569
544
  };
570
545
  }
571
- function createServerlessInstance(config, tools, callTool, state) {
546
+ function createServerlessInstance(config, tools, callTool, state, mcpServer, registry) {
572
547
  const headers = getDefaultHeaders(config.cors);
573
548
  return {
574
549
  async handler(event) {
@@ -634,8 +609,30 @@ function createServerlessInstance(config, tools, callTool, state) {
634
609
  }, headers);
635
610
  }
636
611
  try {
637
- const estimateResponse = await callTool(estimateBody.name, {
638
- inputs: estimateBody.inputs,
612
+ const toolName = estimateBody.name;
613
+ const toolArgs = estimateBody.inputs ?? {};
614
+ // Find tool by name
615
+ let toolKey = null;
616
+ let tool = null;
617
+ for (const [key, t] of Object.entries(registry)) {
618
+ if (t.name === toolName || key === toolName) {
619
+ toolKey = key;
620
+ tool = t;
621
+ break;
622
+ }
623
+ }
624
+ if (!tool || !toolKey) {
625
+ return createResponse(400, {
626
+ error: {
627
+ code: -32602,
628
+ message: `Tool "${toolName}" not found`,
629
+ },
630
+ }, headers);
631
+ }
632
+ // Validate arguments against Zod schema
633
+ const validatedArgs = tool.inputs.parse(toolArgs);
634
+ const estimateResponse = await callTool(toolKey, {
635
+ inputs: validatedArgs,
639
636
  estimate: true,
640
637
  });
641
638
  return createResponse(200, {
@@ -686,7 +683,47 @@ function createServerlessInstance(config, tools, callTool, state) {
686
683
  result = { tools };
687
684
  }
688
685
  else if (rpcMethod === 'tools/call') {
689
- result = await callTool(params?.name, params?.arguments);
686
+ const toolName = params?.name;
687
+ const toolArgs = params?.arguments ?? {};
688
+ // Find tool by name (check both registry key and tool.name)
689
+ let toolKey = null;
690
+ let tool = null;
691
+ for (const [key, t] of Object.entries(registry)) {
692
+ if (t.name === toolName || key === toolName) {
693
+ toolKey = key;
694
+ tool = t;
695
+ break;
696
+ }
697
+ }
698
+ if (!tool || !toolKey) {
699
+ return createResponse(200, {
700
+ jsonrpc: '2.0',
701
+ id,
702
+ error: {
703
+ code: -32602,
704
+ message: `Tool "${toolName}" not found`,
705
+ },
706
+ }, headers);
707
+ }
708
+ // Validate arguments against Zod schema
709
+ try {
710
+ const validatedArgs = tool.inputs.parse(toolArgs);
711
+ result = await callTool(toolKey, {
712
+ inputs: validatedArgs,
713
+ });
714
+ }
715
+ catch (validationError) {
716
+ return createResponse(200, {
717
+ jsonrpc: '2.0',
718
+ id,
719
+ error: {
720
+ code: -32602,
721
+ message: validationError instanceof Error
722
+ ? validationError.message
723
+ : 'Invalid arguments',
724
+ },
725
+ }, headers);
726
+ }
690
727
  }
691
728
  else {
692
729
  return createResponse(200, {
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { CoreApiConfig } from './core/types';
2
+ import type { z } from 'zod';
2
3
  export interface ToolContext {
3
4
  env: Record<string, string | undefined>;
4
5
  mode?: 'execute' | 'estimate';
@@ -15,7 +16,15 @@ export interface ToolExecutionResult<Output = unknown> {
15
16
  billing: BillingInfo;
16
17
  }
17
18
  export type ToolHandler<Input, Output> = (params: ToolParams<Input, Output>) => Promise<ToolExecutionResult<Output>> | ToolExecutionResult<Output>;
18
- export type ToolRegistry = Record<string, ToolHandler<unknown, unknown>>;
19
+ export interface ToolDefinition<Input = unknown, Output = unknown, InputSchema extends z.ZodType<Input> = z.ZodType<Input>, OutputSchema extends z.ZodType<Output> = z.ZodType<Output>> {
20
+ name: string;
21
+ description: string;
22
+ inputs: InputSchema;
23
+ handler: ToolHandler<Input, Output>;
24
+ outputSchema?: OutputSchema;
25
+ [key: string]: unknown;
26
+ }
27
+ export type ToolRegistry = Record<string, ToolDefinition<unknown, unknown>>;
19
28
  export type ToolName<T extends ToolRegistry> = Extract<keyof T, string>;
20
29
  export interface ToolMetadata {
21
30
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,8 @@
23
23
  "author": "Skedyul <support@skedyul.com>",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "^1.0.0"
26
+ "@modelcontextprotocol/sdk": "^1.0.0",
27
+ "zod": "^4.0.0"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/node": "^24.10.1",