@shortcut/mcp 0.4.1 → 0.5.0

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 (2) hide show
  1. package/dist/index.js +540 -76
  2. package/package.json +11 -5
package/dist/index.js CHANGED
@@ -14651,6 +14651,11 @@ class ShortcutClientWrapper {
14651
14651
  await this.loadMembers();
14652
14652
  return userIds.map((id) => this.userCache.get(id)).filter((user) => user !== null);
14653
14653
  }
14654
+ async listMembers() {
14655
+ await this.loadMembers();
14656
+ const members = Array.from(this.userCache.values());
14657
+ return members;
14658
+ }
14654
14659
  async getWorkflowMap(workflowIds) {
14655
14660
  await this.loadWorkflows();
14656
14661
  return new Map(workflowIds.map((id) => [id, this.workflowCache.get(id)]).filter((workflow) => workflow[1] !== null));
@@ -14782,6 +14787,49 @@ class ShortcutClientWrapper {
14782
14787
  throw new Error(`Failed to create the epic: ${response.status}`);
14783
14788
  return epic;
14784
14789
  }
14790
+ async addTaskToStory(storyPublicId, taskParams) {
14791
+ const { description, ownerIds } = taskParams;
14792
+ const params = {
14793
+ description,
14794
+ owner_ids: ownerIds
14795
+ };
14796
+ const response = await this.client.createTask(storyPublicId, params);
14797
+ const task = response?.data ?? null;
14798
+ if (!task)
14799
+ throw new Error(`Failed to create the task: ${response.status}`);
14800
+ return task;
14801
+ }
14802
+ async addRelationToStory(storyPublicId, linkedStoryId) {
14803
+ const response = await this.client.createStoryLink({
14804
+ object_id: linkedStoryId,
14805
+ subject_id: storyPublicId,
14806
+ verb: "relates to"
14807
+ });
14808
+ const storyLink = response?.data ?? null;
14809
+ if (!storyLink)
14810
+ throw new Error(`Failed to create the story links: ${response.status}`);
14811
+ return storyLink;
14812
+ }
14813
+ async getTask(storyPublicId, taskPublicId) {
14814
+ const response = await this.client.getTask(storyPublicId, taskPublicId);
14815
+ const task = response?.data ?? null;
14816
+ if (!task)
14817
+ throw new Error(`Failed to get the task: ${response.status}`);
14818
+ return task;
14819
+ }
14820
+ async updateTask(storyPublicId, taskPublicId, taskParams) {
14821
+ const { description, ownerIds } = taskParams;
14822
+ const params = {
14823
+ description,
14824
+ owner_ids: ownerIds,
14825
+ complete: taskParams.isCompleted
14826
+ };
14827
+ const response = await this.client.updateTask(storyPublicId, taskPublicId, params);
14828
+ const task = response?.data ?? null;
14829
+ if (!task)
14830
+ throw new Error(`Failed to update the task: ${response.status}`);
14831
+ return task;
14832
+ }
14785
14833
  }
14786
14834
 
14787
14835
  // node_modules/zod/lib/index.mjs
@@ -15631,13 +15679,14 @@ var base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_
15631
15679
  var dateRegexSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`;
15632
15680
  var dateRegex = new RegExp(`^${dateRegexSource}$`);
15633
15681
  function timeRegexSource(args) {
15634
- let regex = `([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d`;
15682
+ let secondsRegexSource = `[0-5]\\d`;
15635
15683
  if (args.precision) {
15636
- regex = `${regex}\\.\\d{${args.precision}}`;
15684
+ secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`;
15637
15685
  } else if (args.precision == null) {
15638
- regex = `${regex}(\\.\\d+)?`;
15686
+ secondsRegexSource = `${secondsRegexSource}(\\.\\d+)?`;
15639
15687
  }
15640
- return regex;
15688
+ const secondsQuantifier = args.precision ? "+" : "?";
15689
+ return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`;
15641
15690
  }
15642
15691
  function timeRegex(args) {
15643
15692
  return new RegExp(`^${timeRegexSource(args)}$`);
@@ -18790,18 +18839,20 @@ var z = /* @__PURE__ */ Object.freeze({
18790
18839
  });
18791
18840
 
18792
18841
  // node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
18793
- var LATEST_PROTOCOL_VERSION = "2024-11-05";
18842
+ var LATEST_PROTOCOL_VERSION = "2025-03-26";
18794
18843
  var SUPPORTED_PROTOCOL_VERSIONS = [
18795
18844
  LATEST_PROTOCOL_VERSION,
18845
+ "2024-11-05",
18796
18846
  "2024-10-07"
18797
18847
  ];
18798
18848
  var JSONRPC_VERSION = "2.0";
18799
18849
  var ProgressTokenSchema = z.union([z.string(), z.number().int()]);
18800
18850
  var CursorSchema = z.string();
18851
+ var RequestMetaSchema = z.object({
18852
+ progressToken: z.optional(ProgressTokenSchema)
18853
+ }).passthrough();
18801
18854
  var BaseRequestParamsSchema = z.object({
18802
- _meta: z.optional(z.object({
18803
- progressToken: z.optional(ProgressTokenSchema)
18804
- }).passthrough())
18855
+ _meta: z.optional(RequestMetaSchema)
18805
18856
  }).passthrough();
18806
18857
  var RequestSchema = z.object({
18807
18858
  method: z.string(),
@@ -18822,14 +18873,17 @@ var JSONRPCRequestSchema = z.object({
18822
18873
  jsonrpc: z.literal(JSONRPC_VERSION),
18823
18874
  id: RequestIdSchema
18824
18875
  }).merge(RequestSchema).strict();
18876
+ var isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success;
18825
18877
  var JSONRPCNotificationSchema = z.object({
18826
18878
  jsonrpc: z.literal(JSONRPC_VERSION)
18827
18879
  }).merge(NotificationSchema).strict();
18880
+ var isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success;
18828
18881
  var JSONRPCResponseSchema = z.object({
18829
18882
  jsonrpc: z.literal(JSONRPC_VERSION),
18830
18883
  id: RequestIdSchema,
18831
18884
  result: ResultSchema
18832
18885
  }).strict();
18886
+ var isJSONRPCResponse = (value) => JSONRPCResponseSchema.safeParse(value).success;
18833
18887
  var ErrorCode;
18834
18888
  (function(ErrorCode2) {
18835
18889
  ErrorCode2[ErrorCode2["ConnectionClosed"] = -32000] = "ConnectionClosed";
@@ -18849,6 +18903,7 @@ var JSONRPCErrorSchema = z.object({
18849
18903
  data: z.optional(z.unknown())
18850
18904
  })
18851
18905
  }).strict();
18906
+ var isJSONRPCError = (value) => JSONRPCErrorSchema.safeParse(value).success;
18852
18907
  var JSONRPCMessageSchema = z.union([
18853
18908
  JSONRPCRequestSchema,
18854
18909
  JSONRPCNotificationSchema,
@@ -18885,6 +18940,7 @@ var InitializeRequestSchema = RequestSchema.extend({
18885
18940
  var ServerCapabilitiesSchema = z.object({
18886
18941
  experimental: z.optional(z.object({}).passthrough()),
18887
18942
  logging: z.optional(z.object({}).passthrough()),
18943
+ completions: z.optional(z.object({}).passthrough()),
18888
18944
  prompts: z.optional(z.object({
18889
18945
  listChanged: z.optional(z.boolean())
18890
18946
  }).passthrough()),
@@ -19022,6 +19078,11 @@ var ImageContentSchema = z.object({
19022
19078
  data: z.string().base64(),
19023
19079
  mimeType: z.string()
19024
19080
  }).passthrough();
19081
+ var AudioContentSchema = z.object({
19082
+ type: z.literal("audio"),
19083
+ data: z.string().base64(),
19084
+ mimeType: z.string()
19085
+ }).passthrough();
19025
19086
  var EmbeddedResourceSchema = z.object({
19026
19087
  type: z.literal("resource"),
19027
19088
  resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema])
@@ -19031,6 +19092,7 @@ var PromptMessageSchema = z.object({
19031
19092
  content: z.union([
19032
19093
  TextContentSchema,
19033
19094
  ImageContentSchema,
19095
+ AudioContentSchema,
19034
19096
  EmbeddedResourceSchema
19035
19097
  ])
19036
19098
  }).passthrough();
@@ -19041,13 +19103,21 @@ var GetPromptResultSchema = ResultSchema.extend({
19041
19103
  var PromptListChangedNotificationSchema = NotificationSchema.extend({
19042
19104
  method: z.literal("notifications/prompts/list_changed")
19043
19105
  });
19106
+ var ToolAnnotationsSchema = z.object({
19107
+ title: z.optional(z.string()),
19108
+ readOnlyHint: z.optional(z.boolean()),
19109
+ destructiveHint: z.optional(z.boolean()),
19110
+ idempotentHint: z.optional(z.boolean()),
19111
+ openWorldHint: z.optional(z.boolean())
19112
+ }).passthrough();
19044
19113
  var ToolSchema = z.object({
19045
19114
  name: z.string(),
19046
19115
  description: z.optional(z.string()),
19047
19116
  inputSchema: z.object({
19048
19117
  type: z.literal("object"),
19049
19118
  properties: z.optional(z.object({}).passthrough())
19050
- }).passthrough()
19119
+ }).passthrough(),
19120
+ annotations: z.optional(ToolAnnotationsSchema)
19051
19121
  }).passthrough();
19052
19122
  var ListToolsRequestSchema = PaginatedRequestSchema.extend({
19053
19123
  method: z.literal("tools/list")
@@ -19056,7 +19126,7 @@ var ListToolsResultSchema = PaginatedResultSchema.extend({
19056
19126
  tools: z.array(ToolSchema)
19057
19127
  });
19058
19128
  var CallToolResultSchema = ResultSchema.extend({
19059
- content: z.array(z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema])),
19129
+ content: z.array(z.union([TextContentSchema, ImageContentSchema, AudioContentSchema, EmbeddedResourceSchema])),
19060
19130
  isError: z.boolean().default(false).optional()
19061
19131
  });
19062
19132
  var CompatibilityCallToolResultSchema = CallToolResultSchema.or(ResultSchema.extend({
@@ -19107,7 +19177,7 @@ var ModelPreferencesSchema = z.object({
19107
19177
  }).passthrough();
19108
19178
  var SamplingMessageSchema = z.object({
19109
19179
  role: z.enum(["user", "assistant"]),
19110
- content: z.union([TextContentSchema, ImageContentSchema])
19180
+ content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema])
19111
19181
  }).passthrough();
19112
19182
  var CreateMessageRequestSchema = RequestSchema.extend({
19113
19183
  method: z.literal("sampling/createMessage"),
@@ -19128,7 +19198,8 @@ var CreateMessageResultSchema = ResultSchema.extend({
19128
19198
  role: z.enum(["user", "assistant"]),
19129
19199
  content: z.discriminatedUnion("type", [
19130
19200
  TextContentSchema,
19131
- ImageContentSchema
19201
+ ImageContentSchema,
19202
+ AudioContentSchema
19132
19203
  ])
19133
19204
  });
19134
19205
  var ResourceReferenceSchema = z.object({
@@ -19253,12 +19324,13 @@ class Protocol {
19253
19324
  });
19254
19325
  this.setRequestHandler(PingRequestSchema, (_request) => ({}));
19255
19326
  }
19256
- _setupTimeout(messageId, timeout, maxTotalTimeout, onTimeout) {
19327
+ _setupTimeout(messageId, timeout, maxTotalTimeout, onTimeout, resetTimeoutOnProgress = false) {
19257
19328
  this._timeoutInfo.set(messageId, {
19258
19329
  timeoutId: setTimeout(onTimeout, timeout),
19259
19330
  startTime: Date.now(),
19260
19331
  timeout,
19261
19332
  maxTotalTimeout,
19333
+ resetTimeoutOnProgress,
19262
19334
  onTimeout
19263
19335
  });
19264
19336
  }
@@ -19290,13 +19362,15 @@ class Protocol {
19290
19362
  this._transport.onerror = (error) => {
19291
19363
  this._onerror(error);
19292
19364
  };
19293
- this._transport.onmessage = (message) => {
19294
- if (!("method" in message)) {
19365
+ this._transport.onmessage = (message, extra) => {
19366
+ if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
19295
19367
  this._onresponse(message);
19296
- } else if ("id" in message) {
19297
- this._onrequest(message);
19298
- } else {
19368
+ } else if (isJSONRPCRequest(message)) {
19369
+ this._onrequest(message, extra);
19370
+ } else if (isJSONRPCNotification(message)) {
19299
19371
  this._onnotification(message);
19372
+ } else {
19373
+ this._onerror(new Error(`Unknown message type: ${JSON.stringify(message)}`));
19300
19374
  }
19301
19375
  };
19302
19376
  await this._transport.start();
@@ -19325,8 +19399,8 @@ class Protocol {
19325
19399
  }
19326
19400
  Promise.resolve().then(() => handler(notification)).catch((error) => this._onerror(new Error(`Uncaught error in notification handler: ${error}`)));
19327
19401
  }
19328
- _onrequest(request) {
19329
- var _a, _b;
19402
+ _onrequest(request, extra) {
19403
+ var _a, _b, _c, _d;
19330
19404
  const handler = (_a = this._requestHandlers.get(request.method)) !== null && _a !== undefined ? _a : this.fallbackRequestHandler;
19331
19405
  if (handler === undefined) {
19332
19406
  (_b = this._transport) === null || _b === undefined || _b.send({
@@ -19341,7 +19415,16 @@ class Protocol {
19341
19415
  }
19342
19416
  const abortController = new AbortController;
19343
19417
  this._requestHandlerAbortControllers.set(request.id, abortController);
19344
- Promise.resolve().then(() => handler(request, { signal: abortController.signal })).then((result) => {
19418
+ const fullExtra = {
19419
+ signal: abortController.signal,
19420
+ sessionId: (_c = this._transport) === null || _c === undefined ? undefined : _c.sessionId,
19421
+ _meta: (_d = request.params) === null || _d === undefined ? undefined : _d._meta,
19422
+ sendNotification: (notification) => this.notification(notification, { relatedRequestId: request.id }),
19423
+ sendRequest: (r, resultSchema, options) => this.request(r, resultSchema, { ...options, relatedRequestId: request.id }),
19424
+ authInfo: extra === null || extra === undefined ? undefined : extra.authInfo,
19425
+ requestId: request.id
19426
+ };
19427
+ Promise.resolve().then(() => handler(request, fullExtra)).then((result) => {
19345
19428
  var _a2;
19346
19429
  if (abortController.signal.aborted) {
19347
19430
  return;
@@ -19377,7 +19460,8 @@ class Protocol {
19377
19460
  return;
19378
19461
  }
19379
19462
  const responseHandler = this._responseHandlers.get(messageId);
19380
- if (this._timeoutInfo.has(messageId) && responseHandler) {
19463
+ const timeoutInfo = this._timeoutInfo.get(messageId);
19464
+ if (timeoutInfo && responseHandler && timeoutInfo.resetTimeoutOnProgress) {
19381
19465
  try {
19382
19466
  this._resetTimeout(messageId);
19383
19467
  } catch (error) {
@@ -19397,7 +19481,7 @@ class Protocol {
19397
19481
  this._responseHandlers.delete(messageId);
19398
19482
  this._progressHandlers.delete(messageId);
19399
19483
  this._cleanupTimeout(messageId);
19400
- if ("result" in response) {
19484
+ if (isJSONRPCResponse(response)) {
19401
19485
  handler(response);
19402
19486
  } else {
19403
19487
  const error = new McpError(response.error.code, response.error.message, response.error.data);
@@ -19412,8 +19496,9 @@ class Protocol {
19412
19496
  await ((_a = this._transport) === null || _a === undefined ? undefined : _a.close());
19413
19497
  }
19414
19498
  request(request, resultSchema, options) {
19499
+ const { relatedRequestId, resumptionToken, onresumptiontoken } = options !== null && options !== undefined ? options : {};
19415
19500
  return new Promise((resolve, reject) => {
19416
- var _a, _b, _c, _d;
19501
+ var _a, _b, _c, _d, _e;
19417
19502
  if (!this._transport) {
19418
19503
  reject(new Error("Not connected"));
19419
19504
  return;
@@ -19447,7 +19532,7 @@ class Protocol {
19447
19532
  requestId: messageId,
19448
19533
  reason: String(reason)
19449
19534
  }
19450
- }).catch((error) => this._onerror(new Error(`Failed to send cancellation: ${error}`)));
19535
+ }, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error) => this._onerror(new Error(`Failed to send cancellation: ${error}`)));
19451
19536
  reject(reason);
19452
19537
  };
19453
19538
  this._responseHandlers.set(messageId, (response) => {
@@ -19471,14 +19556,14 @@ class Protocol {
19471
19556
  });
19472
19557
  const timeout = (_d = options === null || options === undefined ? undefined : options.timeout) !== null && _d !== undefined ? _d : DEFAULT_REQUEST_TIMEOUT_MSEC;
19473
19558
  const timeoutHandler = () => cancel(new McpError(ErrorCode.RequestTimeout, "Request timed out", { timeout }));
19474
- this._setupTimeout(messageId, timeout, options === null || options === undefined ? undefined : options.maxTotalTimeout, timeoutHandler);
19475
- this._transport.send(jsonrpcRequest).catch((error) => {
19559
+ this._setupTimeout(messageId, timeout, options === null || options === undefined ? undefined : options.maxTotalTimeout, timeoutHandler, (_e = options === null || options === undefined ? undefined : options.resetTimeoutOnProgress) !== null && _e !== undefined ? _e : false);
19560
+ this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error) => {
19476
19561
  this._cleanupTimeout(messageId);
19477
19562
  reject(error);
19478
19563
  });
19479
19564
  });
19480
19565
  }
19481
- async notification(notification) {
19566
+ async notification(notification, options) {
19482
19567
  if (!this._transport) {
19483
19568
  throw new Error("Not connected");
19484
19569
  }
@@ -19487,12 +19572,14 @@ class Protocol {
19487
19572
  ...notification,
19488
19573
  jsonrpc: "2.0"
19489
19574
  };
19490
- await this._transport.send(jsonrpcNotification);
19575
+ await this._transport.send(jsonrpcNotification, options);
19491
19576
  }
19492
19577
  setRequestHandler(requestSchema, handler) {
19493
19578
  const method = requestSchema.shape.method.value;
19494
19579
  this.assertRequestHandlerCapability(method);
19495
- this._requestHandlers.set(method, (request, extra) => Promise.resolve(handler(requestSchema.parse(request), extra)));
19580
+ this._requestHandlers.set(method, (request, extra) => {
19581
+ return Promise.resolve(handler(requestSchema.parse(request), extra));
19582
+ });
19496
19583
  }
19497
19584
  removeRequestHandler(method) {
19498
19585
  this._requestHandlers.delete(method);
@@ -20932,16 +21019,19 @@ class McpServer {
20932
21019
  this.server.assertCanSetRequestHandler(ListToolsRequestSchema.shape.method.value);
20933
21020
  this.server.assertCanSetRequestHandler(CallToolRequestSchema.shape.method.value);
20934
21021
  this.server.registerCapabilities({
20935
- tools: {}
21022
+ tools: {
21023
+ listChanged: true
21024
+ }
20936
21025
  });
20937
21026
  this.server.setRequestHandler(ListToolsRequestSchema, () => ({
20938
- tools: Object.entries(this._registeredTools).map(([name, tool]) => {
21027
+ tools: Object.entries(this._registeredTools).filter(([, tool]) => tool.enabled).map(([name, tool]) => {
20939
21028
  return {
20940
21029
  name,
20941
21030
  description: tool.description,
20942
21031
  inputSchema: tool.inputSchema ? zodToJsonSchema(tool.inputSchema, {
20943
21032
  strictUnions: true
20944
- }) : EMPTY_OBJECT_JSON_SCHEMA
21033
+ }) : EMPTY_OBJECT_JSON_SCHEMA,
21034
+ annotations: tool.annotations
20945
21035
  };
20946
21036
  })
20947
21037
  }));
@@ -20950,6 +21040,9 @@ class McpServer {
20950
21040
  if (!tool) {
20951
21041
  throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
20952
21042
  }
21043
+ if (!tool.enabled) {
21044
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
21045
+ }
20953
21046
  if (tool.inputSchema) {
20954
21047
  const parseResult = await tool.inputSchema.safeParseAsync(request.params.arguments);
20955
21048
  if (!parseResult.success) {
@@ -21009,7 +21102,10 @@ class McpServer {
21009
21102
  async handlePromptCompletion(request, ref) {
21010
21103
  const prompt = this._registeredPrompts[ref.name];
21011
21104
  if (!prompt) {
21012
- throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.ref.name} not found`);
21105
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
21106
+ }
21107
+ if (!prompt.enabled) {
21108
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} disabled`);
21013
21109
  }
21014
21110
  if (!prompt.argsSchema) {
21015
21111
  return EMPTY_COMPLETION_RESULT;
@@ -21045,10 +21141,12 @@ class McpServer {
21045
21141
  this.server.assertCanSetRequestHandler(ListResourceTemplatesRequestSchema.shape.method.value);
21046
21142
  this.server.assertCanSetRequestHandler(ReadResourceRequestSchema.shape.method.value);
21047
21143
  this.server.registerCapabilities({
21048
- resources: {}
21144
+ resources: {
21145
+ listChanged: true
21146
+ }
21049
21147
  });
21050
21148
  this.server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
21051
- const resources = Object.entries(this._registeredResources).map(([uri, resource]) => ({
21149
+ const resources = Object.entries(this._registeredResources).filter(([_, resource]) => resource.enabled).map(([uri, resource]) => ({
21052
21150
  uri,
21053
21151
  name: resource.name,
21054
21152
  ...resource.metadata
@@ -21080,6 +21178,9 @@ class McpServer {
21080
21178
  const uri = new URL(request.params.uri);
21081
21179
  const resource = this._registeredResources[uri.toString()];
21082
21180
  if (resource) {
21181
+ if (!resource.enabled) {
21182
+ throw new McpError(ErrorCode.InvalidParams, `Resource ${uri} disabled`);
21183
+ }
21083
21184
  return resource.readCallback(uri, extra);
21084
21185
  }
21085
21186
  for (const template of Object.values(this._registeredResourceTemplates)) {
@@ -21100,10 +21201,12 @@ class McpServer {
21100
21201
  this.server.assertCanSetRequestHandler(ListPromptsRequestSchema.shape.method.value);
21101
21202
  this.server.assertCanSetRequestHandler(GetPromptRequestSchema.shape.method.value);
21102
21203
  this.server.registerCapabilities({
21103
- prompts: {}
21204
+ prompts: {
21205
+ listChanged: true
21206
+ }
21104
21207
  });
21105
21208
  this.server.setRequestHandler(ListPromptsRequestSchema, () => ({
21106
- prompts: Object.entries(this._registeredPrompts).map(([name, prompt]) => {
21209
+ prompts: Object.entries(this._registeredPrompts).filter(([, prompt]) => prompt.enabled).map(([name, prompt]) => {
21107
21210
  return {
21108
21211
  name,
21109
21212
  description: prompt.description,
@@ -21116,6 +21219,9 @@ class McpServer {
21116
21219
  if (!prompt) {
21117
21220
  throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
21118
21221
  }
21222
+ if (!prompt.enabled) {
21223
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} disabled`);
21224
+ }
21119
21225
  if (prompt.argsSchema) {
21120
21226
  const parseResult = await prompt.argsSchema.safeParseAsync(request.params.arguments);
21121
21227
  if (!parseResult.success) {
@@ -21142,22 +21248,69 @@ class McpServer {
21142
21248
  if (this._registeredResources[uriOrTemplate]) {
21143
21249
  throw new Error(`Resource ${uriOrTemplate} is already registered`);
21144
21250
  }
21145
- this._registeredResources[uriOrTemplate] = {
21251
+ const registeredResource = {
21146
21252
  name,
21147
21253
  metadata,
21148
- readCallback
21254
+ readCallback,
21255
+ enabled: true,
21256
+ disable: () => registeredResource.update({ enabled: false }),
21257
+ enable: () => registeredResource.update({ enabled: true }),
21258
+ remove: () => registeredResource.update({ uri: null }),
21259
+ update: (updates) => {
21260
+ if (typeof updates.uri !== "undefined" && updates.uri !== uriOrTemplate) {
21261
+ delete this._registeredResources[uriOrTemplate];
21262
+ if (updates.uri)
21263
+ this._registeredResources[updates.uri] = registeredResource;
21264
+ }
21265
+ if (typeof updates.name !== "undefined")
21266
+ registeredResource.name = updates.name;
21267
+ if (typeof updates.metadata !== "undefined")
21268
+ registeredResource.metadata = updates.metadata;
21269
+ if (typeof updates.callback !== "undefined")
21270
+ registeredResource.readCallback = updates.callback;
21271
+ if (typeof updates.enabled !== "undefined")
21272
+ registeredResource.enabled = updates.enabled;
21273
+ this.sendResourceListChanged();
21274
+ }
21149
21275
  };
21276
+ this._registeredResources[uriOrTemplate] = registeredResource;
21277
+ this.setResourceRequestHandlers();
21278
+ this.sendResourceListChanged();
21279
+ return registeredResource;
21150
21280
  } else {
21151
21281
  if (this._registeredResourceTemplates[name]) {
21152
21282
  throw new Error(`Resource template ${name} is already registered`);
21153
21283
  }
21154
- this._registeredResourceTemplates[name] = {
21284
+ const registeredResourceTemplate = {
21155
21285
  resourceTemplate: uriOrTemplate,
21156
21286
  metadata,
21157
- readCallback
21287
+ readCallback,
21288
+ enabled: true,
21289
+ disable: () => registeredResourceTemplate.update({ enabled: false }),
21290
+ enable: () => registeredResourceTemplate.update({ enabled: true }),
21291
+ remove: () => registeredResourceTemplate.update({ name: null }),
21292
+ update: (updates) => {
21293
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
21294
+ delete this._registeredResourceTemplates[name];
21295
+ if (updates.name)
21296
+ this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
21297
+ }
21298
+ if (typeof updates.template !== "undefined")
21299
+ registeredResourceTemplate.resourceTemplate = updates.template;
21300
+ if (typeof updates.metadata !== "undefined")
21301
+ registeredResourceTemplate.metadata = updates.metadata;
21302
+ if (typeof updates.callback !== "undefined")
21303
+ registeredResourceTemplate.readCallback = updates.callback;
21304
+ if (typeof updates.enabled !== "undefined")
21305
+ registeredResourceTemplate.enabled = updates.enabled;
21306
+ this.sendResourceListChanged();
21307
+ }
21158
21308
  };
21309
+ this._registeredResourceTemplates[name] = registeredResourceTemplate;
21310
+ this.setResourceRequestHandlers();
21311
+ this.sendResourceListChanged();
21312
+ return registeredResourceTemplate;
21159
21313
  }
21160
- this.setResourceRequestHandlers();
21161
21314
  }
21162
21315
  tool(name, ...rest) {
21163
21316
  if (this._registeredTools[name]) {
@@ -21168,16 +21321,51 @@ class McpServer {
21168
21321
  description = rest.shift();
21169
21322
  }
21170
21323
  let paramsSchema;
21324
+ let annotations;
21171
21325
  if (rest.length > 1) {
21172
- paramsSchema = rest.shift();
21326
+ const firstArg = rest[0];
21327
+ if (isZodRawShape(firstArg)) {
21328
+ paramsSchema = rest.shift();
21329
+ if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !isZodRawShape(rest[0])) {
21330
+ annotations = rest.shift();
21331
+ }
21332
+ } else if (typeof firstArg === "object" && firstArg !== null) {
21333
+ annotations = rest.shift();
21334
+ }
21173
21335
  }
21174
21336
  const cb = rest[0];
21175
- this._registeredTools[name] = {
21337
+ const registeredTool = {
21176
21338
  description,
21177
21339
  inputSchema: paramsSchema === undefined ? undefined : z.object(paramsSchema),
21178
- callback: cb
21340
+ annotations,
21341
+ callback: cb,
21342
+ enabled: true,
21343
+ disable: () => registeredTool.update({ enabled: false }),
21344
+ enable: () => registeredTool.update({ enabled: true }),
21345
+ remove: () => registeredTool.update({ name: null }),
21346
+ update: (updates) => {
21347
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
21348
+ delete this._registeredTools[name];
21349
+ if (updates.name)
21350
+ this._registeredTools[updates.name] = registeredTool;
21351
+ }
21352
+ if (typeof updates.description !== "undefined")
21353
+ registeredTool.description = updates.description;
21354
+ if (typeof updates.paramsSchema !== "undefined")
21355
+ registeredTool.inputSchema = z.object(updates.paramsSchema);
21356
+ if (typeof updates.callback !== "undefined")
21357
+ registeredTool.callback = updates.callback;
21358
+ if (typeof updates.annotations !== "undefined")
21359
+ registeredTool.annotations = updates.annotations;
21360
+ if (typeof updates.enabled !== "undefined")
21361
+ registeredTool.enabled = updates.enabled;
21362
+ this.sendToolListChanged();
21363
+ }
21179
21364
  };
21365
+ this._registeredTools[name] = registeredTool;
21180
21366
  this.setToolRequestHandlers();
21367
+ this.sendToolListChanged();
21368
+ return registeredTool;
21181
21369
  }
21182
21370
  prompt(name, ...rest) {
21183
21371
  if (this._registeredPrompts[name]) {
@@ -21192,17 +21380,67 @@ class McpServer {
21192
21380
  argsSchema = rest.shift();
21193
21381
  }
21194
21382
  const cb = rest[0];
21195
- this._registeredPrompts[name] = {
21383
+ const registeredPrompt = {
21196
21384
  description,
21197
21385
  argsSchema: argsSchema === undefined ? undefined : z.object(argsSchema),
21198
- callback: cb
21386
+ callback: cb,
21387
+ enabled: true,
21388
+ disable: () => registeredPrompt.update({ enabled: false }),
21389
+ enable: () => registeredPrompt.update({ enabled: true }),
21390
+ remove: () => registeredPrompt.update({ name: null }),
21391
+ update: (updates) => {
21392
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
21393
+ delete this._registeredPrompts[name];
21394
+ if (updates.name)
21395
+ this._registeredPrompts[updates.name] = registeredPrompt;
21396
+ }
21397
+ if (typeof updates.description !== "undefined")
21398
+ registeredPrompt.description = updates.description;
21399
+ if (typeof updates.argsSchema !== "undefined")
21400
+ registeredPrompt.argsSchema = z.object(updates.argsSchema);
21401
+ if (typeof updates.callback !== "undefined")
21402
+ registeredPrompt.callback = updates.callback;
21403
+ if (typeof updates.enabled !== "undefined")
21404
+ registeredPrompt.enabled = updates.enabled;
21405
+ this.sendPromptListChanged();
21406
+ }
21199
21407
  };
21408
+ this._registeredPrompts[name] = registeredPrompt;
21200
21409
  this.setPromptRequestHandlers();
21410
+ this.sendPromptListChanged();
21411
+ return registeredPrompt;
21412
+ }
21413
+ isConnected() {
21414
+ return this.server.transport !== undefined;
21415
+ }
21416
+ sendResourceListChanged() {
21417
+ if (this.isConnected()) {
21418
+ this.server.sendResourceListChanged();
21419
+ }
21420
+ }
21421
+ sendToolListChanged() {
21422
+ if (this.isConnected()) {
21423
+ this.server.sendToolListChanged();
21424
+ }
21425
+ }
21426
+ sendPromptListChanged() {
21427
+ if (this.isConnected()) {
21428
+ this.server.sendPromptListChanged();
21429
+ }
21201
21430
  }
21202
21431
  }
21203
21432
  var EMPTY_OBJECT_JSON_SCHEMA = {
21204
21433
  type: "object"
21205
21434
  };
21435
+ function isZodRawShape(obj) {
21436
+ if (typeof obj !== "object" || obj === null)
21437
+ return false;
21438
+ const isEmptyObject = Object.keys(obj).length === 0;
21439
+ return isEmptyObject || Object.values(obj).some(isZodTypeLike);
21440
+ }
21441
+ function isZodTypeLike(value) {
21442
+ return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
21443
+ }
21206
21444
  function promptArgumentsFromSchema(schema) {
21207
21445
  return Object.entries(schema.shape).map(([name, field]) => ({
21208
21446
  name,
@@ -21243,7 +21481,7 @@ class ReadBuffer {
21243
21481
  if (index === -1) {
21244
21482
  return null;
21245
21483
  }
21246
- const line = this._buffer.toString("utf8", 0, index);
21484
+ const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
21247
21485
  this._buffer = this._buffer.subarray(index + 1);
21248
21486
  return deserializeMessage(line);
21249
21487
  }
@@ -21325,7 +21563,7 @@ var import_client = __toESM(require_lib(), 1);
21325
21563
 
21326
21564
  // package.json
21327
21565
  var name = "@shortcut/mcp";
21328
- var version = "0.4.1";
21566
+ var version = "0.5.0";
21329
21567
 
21330
21568
  // src/tools/base.ts
21331
21569
  class BaseTools {
@@ -21339,13 +21577,98 @@ class BaseTools {
21339
21577
  }
21340
21578
 
21341
21579
  // src/tools/utils/format.ts
21580
+ function jsonToText(data, options = {}) {
21581
+ const indent = options.indent || "";
21582
+ if (data === null || data === undefined)
21583
+ return "";
21584
+ if (Array.isArray(data))
21585
+ return formatArray(data, { ...options, indent });
21586
+ if (typeof data === "object")
21587
+ return formatObject(data, { ...options, indent });
21588
+ return formatPrimitive(data);
21589
+ }
21590
+ function formatPrimitive(value) {
21591
+ if (typeof value === "boolean")
21592
+ return value ? "Yes" : "No";
21593
+ return String(value);
21594
+ }
21595
+ function formatArray(arr, options = {}) {
21596
+ if (arr.length === 0)
21597
+ return "(empty)";
21598
+ const indent = options.indent || "";
21599
+ const nextIndent = `${indent} `;
21600
+ return arr.map((item) => {
21601
+ let formattedItem;
21602
+ if (typeof item === "object" && item !== null) {
21603
+ formattedItem = jsonToText(item, {
21604
+ ...options,
21605
+ indent: nextIndent,
21606
+ depth: options.depth !== undefined ? options.depth - 1 : undefined
21607
+ });
21608
+ if (formattedItem.includes(`
21609
+ `))
21610
+ return `${indent}-
21611
+ ${formattedItem}`;
21612
+ } else
21613
+ formattedItem = formatPrimitive(item);
21614
+ return `${indent}- ${formattedItem}`;
21615
+ }).join(`
21616
+ `);
21617
+ }
21618
+ function formatObject(obj, options = {}) {
21619
+ const indent = options.indent || "";
21620
+ const nextIndent = `${indent} `;
21621
+ if (options.depth !== undefined && options.depth <= 0)
21622
+ return `${indent}[Object]`;
21623
+ if (Object.keys(obj).length === 0)
21624
+ return `${indent}(empty)`;
21625
+ let keys;
21626
+ if (!options.include) {
21627
+ keys = Object.keys(obj);
21628
+ } else if (Array.isArray(options.include)) {
21629
+ const arr = options.include;
21630
+ keys = Object.keys(obj).filter((key) => arr.includes(key));
21631
+ } else {
21632
+ keys = Object.keys(obj).filter((key) => {
21633
+ const include = options.include;
21634
+ return key in include;
21635
+ });
21636
+ }
21637
+ return keys.map((key) => {
21638
+ const value = obj[key];
21639
+ const formattedKey = formatKey(key);
21640
+ let nestedInclude;
21641
+ if (options.include && !Array.isArray(options.include)) {
21642
+ const includeValue = options.include[key];
21643
+ if (includeValue === true)
21644
+ nestedInclude = undefined;
21645
+ else
21646
+ nestedInclude = includeValue;
21647
+ }
21648
+ const formattedValue = jsonToText(value, {
21649
+ ...options,
21650
+ include: nestedInclude,
21651
+ indent: nextIndent,
21652
+ depth: options.depth !== undefined ? options.depth - 1 : undefined
21653
+ });
21654
+ if (!formattedValue.includes(`
21655
+ `)) {
21656
+ return `${indent}${formattedKey}: ${formattedValue}`;
21657
+ }
21658
+ return `${indent}${formattedKey}:
21659
+ ${formattedValue}`;
21660
+ }).join(`
21661
+ `);
21662
+ }
21663
+ function formatKey(key) {
21664
+ return key.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
21665
+ }
21342
21666
  var formatAsUnorderedList = (items, label) => {
21343
21667
  return `${label ? `${label}:` : ""}${items?.length ? `${label ? `
21344
- ` : ""}${items.map((item) => `- ${item}`).join(`
21345
- `)}` : `${label ? " " : ""}[None]`}`;
21668
+ ` : ""}${formatArray(items)}` : `${label ? " " : ""}(none)`}`;
21346
21669
  };
21347
21670
  var formatStoryList = (stories, users, label) => {
21348
- return formatAsUnorderedList(stories.map((story) => `sc-${story.id}: ${story.name} (Type: ${story.story_type}, State: ${story.completed ? "Completed" : story.started ? "In Progress" : "Not Started"}, Team: ${story.group_id ? `${story.group_id}` : "[None]"}, Epic: ${story.epic_id ? `${story.epic_id}` : "[None]"}, Iteration: ${story.iteration_id ? `${story.iteration_id}` : "[None]"}, Owners: ${story.owner_ids.map((ownerId) => users.get(ownerId)).filter((owner) => owner !== null).map((owner) => `@${owner.profile.mention_name}`).join(", ") || "[None]"})`), label);
21671
+ return formatAsUnorderedList(stories.map((story) => `sc-${story.id}: ${story.name} (Type: ${story.story_type}, State: ${story.completed ? "Completed" : story.started ? "In Progress" : "Not Started"}, Team: ${story.group_id ? `${story.group_id}` : "(none)"}, Epic: ${story.epic_id ? `${story.epic_id}` : "(none)"}, Iteration: ${story.iteration_id ? `${story.iteration_id}` : "(none)"}, Owners: ${story.owner_ids.map((ownerId) => users.get(ownerId)).filter((owner) => owner !== null).map((owner) => `@${owner.profile.mention_name}`).join(", ") || "(none)"})`), label);
21349
21672
  };
21350
21673
  var formatMemberList = (ids, users, label = "Members") => {
21351
21674
  return formatAsUnorderedList((ids || []).map((id) => {
@@ -21382,6 +21705,9 @@ var formatStats = (stats, showPoints) => {
21382
21705
  - (${stats.num_stories_unestimated} of the stories are unestimated)`;
21383
21706
  return statsString;
21384
21707
  };
21708
+ var formatUsersList = (users) => {
21709
+ return formatAsUnorderedList(users.map((user) => `id=${user.id} ${user?.profile?.mention_name ? `@${user.profile.mention_name}` : ""} : ""}`));
21710
+ };
21385
21711
 
21386
21712
  // src/tools/utils/search.ts
21387
21713
  var getKey = (prop) => {
@@ -21394,8 +21720,11 @@ var getKey = (prop) => {
21394
21720
  var buildSearchQuery = async (params, currentUser) => {
21395
21721
  const query = Object.entries(params).map(([key, value]) => {
21396
21722
  const q = getKey(key);
21397
- if ((key === "owner" || key === "requester") && value === "me")
21398
- return `${q}:${currentUser?.mention_name || value}`;
21723
+ if (key === "owner" || key === "requester") {
21724
+ if (value === "me")
21725
+ return `${q}:${currentUser?.mention_name || value}`;
21726
+ return `${q}:${String(value || "").replace(/^@/, "")}`;
21727
+ }
21399
21728
  if (typeof value === "boolean")
21400
21729
  return value ? q : `!${q}`;
21401
21730
  if (typeof value === "number")
@@ -21429,9 +21758,9 @@ var variations = [
21429
21758
  range({ f: dateformat, t: dateformat })
21430
21759
  ];
21431
21760
  var DATE_REGEXP = new RegExp(`^(${variations.join("|")})$`);
21432
- var date2 = z.string().regex(DATE_REGEXP).optional().describe('The date in "YYYY-MM-DD" format, or one of the keywords: "yesterday", "today", "tomorrow", or a date range in the format "YYYY-MM-DD..YYYY-MM-DD". The date range can also be open ended by using "*" for one of the bounds. The date range can also use the keywords, but the range has to be open ended when keywords are used. For relative dates. The keywords cannot be used to calculate relative dates (e.g. the following are not valid: "today-1" or "tomorrow+1").');
21761
+ var date2 = z.string().regex(DATE_REGEXP).optional().describe('The date in "YYYY-MM-DD" format, or one of the keywords: "yesterday", "today", "tomorrow", or a date range in the format "YYYY-MM-DD..YYYY-MM-DD". The date range can also be open ended by using "*" for one of the bounds. Examples: "2023-01-01", "today", "2023-01-01..*" (from Jan 1, 2023 to any future date), "*.2023-01-31" (any date up to Jan 31, 2023), "today..*" (from today onwards), "*.yesterday" (any date up to yesterday). The keywords cannot be used to calculate relative dates (e.g. the following are not valid: "today-1" or "tomorrow+1").');
21433
21762
  var is = (field) => z.boolean().optional().describe(`Find only entities that are ${field} when true, or only entities that are not ${field} when false.`);
21434
- var has = (field) => z.boolean().optional().describe(`Find only entities that have ${field} when true, or only entities that do not have ${field} when false.`);
21763
+ var has = (field) => z.boolean().optional().describe(`Find only entities that have ${field} when true, or only entities that do not have ${field} when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner.`);
21435
21764
  var user = (field) => z.string().optional().describe(`Find entities where the ${field} match the specified user. This must either be the user's mention name or the keyword "me" for the current user.`);
21436
21765
 
21437
21766
  // src/tools/epics.ts
@@ -21467,7 +21796,7 @@ class EpicTools extends BaseTools {
21467
21796
  name: z.string().describe("The name of the epic"),
21468
21797
  owner: z.string().optional().describe("The user ID of the owner of the epic"),
21469
21798
  description: z.string().optional().describe("A description of the epic"),
21470
- team: z.string().optional().describe("The ID of a team to assign the epic to")
21799
+ teamId: z.string().optional().describe("The ID of a team to assign the epic to")
21471
21800
  }, async (params) => await tools.createEpic(params));
21472
21801
  return tools;
21473
21802
  }
@@ -21495,8 +21824,8 @@ Archived: ${epic.archived ? "Yes" : "No"}
21495
21824
  Completed: ${epic.completed ? "Yes" : "No"}
21496
21825
  Started: ${epic.started ? "Yes" : "No"}
21497
21826
  Due date: ${epic.deadline ? epic.deadline : "[Not set]"}
21498
- Team: ${epic.group_id ? `${epic.group_id}` : "[None]"}
21499
- Objective: ${epic.milestone_id ? `${epic.milestone_id}` : "[None]"}
21827
+ Team: ${epic.group_id ? `${epic.group_id}` : "(none)"}
21828
+ Objective: ${epic.milestone_id ? `${epic.milestone_id}` : "(none)"}
21500
21829
 
21501
21830
  ${formatStats(epic.stats, showPoints)}
21502
21831
 
@@ -21506,7 +21835,7 @@ ${epic.description}`);
21506
21835
  async createEpic({
21507
21836
  name: name2,
21508
21837
  owner,
21509
- team: group_id,
21838
+ teamId: group_id,
21510
21839
  description
21511
21840
  }) {
21512
21841
  const epic = await this.client.createEpic({
@@ -21532,7 +21861,7 @@ class IterationTools extends BaseTools {
21532
21861
  name: z.string().optional().describe("Find only iterations matching the specified name"),
21533
21862
  description: z.string().optional().describe("Find only iterations matching the specified description"),
21534
21863
  state: z.enum(["started", "unstarted", "done"]).optional().describe("Find only iterations matching the specified state"),
21535
- team: z.string().optional().describe("Find only iterations matching the specified team. Should be a team mention name."),
21864
+ team: z.string().optional().describe("Find only iterations matching the specified team. This can be a team ID or mention name."),
21536
21865
  created: date2,
21537
21866
  updated: date2,
21538
21867
  startDate: date2,
@@ -21540,9 +21869,9 @@ class IterationTools extends BaseTools {
21540
21869
  }, async (params) => await tools.searchIterations(params));
21541
21870
  server.tool("create-iteration", "Create a new Shortcut iteration", {
21542
21871
  name: z.string().describe("The name of the iteration"),
21543
- startDate: z.string().describe("The start date of the iteration"),
21544
- endDate: z.string().describe("The end date of the iteration"),
21545
- team: z.string().optional().describe("The ID of a team to assign the iteration to"),
21872
+ startDate: z.string().describe("The start date of the iteration in YYYY-MM-DD format"),
21873
+ endDate: z.string().describe("The end date of the iteration in YYYY-MM-DD format"),
21874
+ teamId: z.string().optional().describe("The ID of a team to assign the iteration to"),
21546
21875
  description: z.string().optional().describe("A description of the iteration")
21547
21876
  }, async (params) => await tools.createIteration(params));
21548
21877
  return tools;
@@ -21579,7 +21908,7 @@ Start date: ${iteration.start_date}
21579
21908
  End date: ${iteration.end_date}
21580
21909
  Completed: ${iteration.status === "completed" ? "Yes" : "No"}
21581
21910
  Started: ${iteration.status === "started" ? "Yes" : "No"}
21582
- Team: ${iteration.group_ids?.length ? `${iteration.group_ids.join(", ")}` : "[None]"}
21911
+ Team: ${iteration.group_ids?.length ? `${iteration.group_ids.join(", ")}` : "(none)"}
21583
21912
 
21584
21913
  ${formatStats(iteration.stats, showPoints)}
21585
21914
 
@@ -21590,14 +21919,14 @@ ${iteration.description}`);
21590
21919
  name: name2,
21591
21920
  startDate,
21592
21921
  endDate,
21593
- team,
21922
+ teamId,
21594
21923
  description
21595
21924
  }) {
21596
21925
  const iteration = await this.client.createIteration({
21597
21926
  name: name2,
21598
21927
  start_date: startDate,
21599
21928
  end_date: endDate,
21600
- group_ids: team ? [team] : undefined,
21929
+ group_ids: teamId ? [teamId] : undefined,
21601
21930
  description
21602
21931
  });
21603
21932
  if (!iteration)
@@ -21663,7 +21992,7 @@ ${objective.description}`);
21663
21992
  class StoryTools extends BaseTools {
21664
21993
  static create(client, server) {
21665
21994
  const tools = new StoryTools(client);
21666
- server.tool("get-story-branch-name", 'Get a valid branch name for a specific story. The branch name is a combination of story ID, owner, and story name in the format "[owner]/sc-[id]/[name]". The story name will be truncated if the total length of the branch name exceeds 50 characters.', {
21995
+ server.tool("get-story-branch-name", "Get a valid branch name for a specific story.", {
21667
21996
  storyPublicId: z.number().positive().describe("The public Id of the story")
21668
21997
  }, async ({ storyPublicId }) => await tools.getStoryBranchName(storyPublicId));
21669
21998
  server.tool("get-story", "Get a Shortcut story by public ID", {
@@ -21716,7 +22045,9 @@ class StoryTools extends BaseTools {
21716
22045
  due: date2
21717
22046
  }, async (params) => await tools.searchStories(params));
21718
22047
  server.tool("create-story", `Create a new Shortcut story.
21719
- Name and Workflow are required. If a team is specified, the workflow is optional, and we will use the default workflow for that team instead.
22048
+ Name is required, and either a Team or Workflow must be specified:
22049
+ - If only Team is specified, we will use the default workflow for that team.
22050
+ - If Workflow is specified, it will be used regardless of Team.
21720
22051
  The story will be added to the default state for the workflow.
21721
22052
  `, {
21722
22053
  name: z.string().min(1).max(512).describe("The name of the story. Required."),
@@ -21724,8 +22055,8 @@ The story will be added to the default state for the workflow.
21724
22055
  type: z.enum(["feature", "bug", "chore"]).default("feature").describe("The type of the story"),
21725
22056
  owner: z.string().optional().describe("The user id of the owner of the story"),
21726
22057
  epic: z.number().optional().describe("The epic id of the epic the story belongs to"),
21727
- team: z.string().optional().describe("The team id of the team the story belongs to. Required unless a workflow is specified."),
21728
- workflow: z.number().optional().describe("The workflow to add the story to. Required unless a team is specified.")
22058
+ team: z.string().optional().describe("The team ID or mention name of the team the story belongs to. Required unless a workflow is specified."),
22059
+ workflow: z.number().optional().describe("The workflow ID to add the story to. Required unless a team is specified.")
21729
22060
  }, async ({ name: name2, description, type, owner, epic, team, workflow }) => await tools.createStory({
21730
22061
  name: name2,
21731
22062
  description,
@@ -21735,6 +22066,21 @@ The story will be added to the default state for the workflow.
21735
22066
  team,
21736
22067
  workflow
21737
22068
  }));
22069
+ server.tool("update-story", "Update an existing Shortcut story. Only provide fields you want to update. The story public ID will always be included in updates.", {
22070
+ storyPublicId: z.number().positive().describe("The public ID of the story to update"),
22071
+ name: z.string().max(512).optional().describe("The name of the story"),
22072
+ description: z.string().max(1e4).optional().describe("The description of the story"),
22073
+ type: z.enum(["feature", "bug", "chore"]).optional().describe("The type of the story"),
22074
+ epic: z.number().nullable().optional().describe("The epic id of the epic the story belongs to, or null to unset"),
22075
+ estimate: z.number().nullable().optional().describe("The point estimate of the story, or null to unset"),
22076
+ owner_ids: z.array(z.string()).optional().describe("Array of user UUIDs to assign as owners of the story"),
22077
+ workflow_state_id: z.number().optional().describe("The workflow state ID to move the story to"),
22078
+ labels: z.array(z.object({
22079
+ name: z.string().describe("The name of the label"),
22080
+ color: z.string().optional().describe("The color of the label"),
22081
+ description: z.string().optional().describe("The description of the label")
22082
+ })).optional().describe("Labels to assign to the story")
22083
+ }, async (params) => await tools.updateStory(params));
21738
22084
  server.tool("assign-current-user-as-owner", "Assign the current user as the owner of a story", {
21739
22085
  storyPublicId: z.number().positive().describe("The public ID of the story")
21740
22086
  }, async ({ storyPublicId }) => await tools.assignCurrentUserAsOwner(storyPublicId));
@@ -21745,6 +22091,22 @@ The story will be added to the default state for the workflow.
21745
22091
  storyPublicId: z.number().positive().describe("The public ID of the story"),
21746
22092
  text: z.string().min(1).describe("The text of the comment")
21747
22093
  }, async (params) => await tools.createStoryComment(params));
22094
+ server.tool("add-task-to-story", "Add a task to a story", {
22095
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22096
+ taskDescription: z.string().min(1).describe("The description of the task"),
22097
+ taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task")
22098
+ }, async (params) => await tools.addTaskToStory(params));
22099
+ server.tool("add-relation-to-story", "Add a relation to a story", {
22100
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22101
+ relatedStoryPublicId: z.number().positive().describe("The public ID of the related story")
22102
+ }, async (params) => await tools.addRelationToStory(params));
22103
+ server.tool("update-task", "Update a task in a story", {
22104
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22105
+ taskPublicId: z.number().positive().describe("The public ID of the task"),
22106
+ taskDescription: z.string().optional().describe("The description of the task"),
22107
+ taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task"),
22108
+ isCompleted: z.boolean().optional().describe("Whether the task is completed or not")
22109
+ }, async (params) => await tools.updateTask(params));
21748
22110
  return tools;
21749
22111
  }
21750
22112
  async assignCurrentUserAsOwner(storyPublicId) {
@@ -21849,11 +22211,11 @@ Completed: ${story.completed ? "Yes" : "No"}
21849
22211
  Started: ${story.started ? "Yes" : "No"}
21850
22212
  Blocked: ${story.blocked ? "Yes" : "No"}
21851
22213
  Blocking: ${story.blocker ? "Yes" : "No"}
21852
- Due date: ${story.deadline ? story.deadline : "[None]"}
21853
- Team: ${story.group_id ? `${story.group_id}` : "[None]"}
22214
+ Due date: ${story.deadline ? story.deadline : "(none)"}
22215
+ Team: ${story.group_id ? `${story.group_id}` : "(none)"}
21854
22216
  ${formatMemberList(story.owner_ids, users, "Owners")}
21855
- Epic: ${story.epic_id ? `${story.epic_id}` : "[None]"}
21856
- Iteration: ${story.iteration_id ? `${story.iteration_id}` : "[None]"}
22217
+ Epic: ${story.epic_id ? `${story.epic_id}` : "(none)"}
22218
+ Iteration: ${story.iteration_id ? `${story.iteration_id}` : "(none)"}
21857
22219
 
21858
22220
  Description:
21859
22221
  ${story.description}
@@ -21887,6 +22249,103 @@ ${comment.text || ""}`;
21887
22249
  const storyComment = await this.client.createStoryComment(storyPublicId, { text });
21888
22250
  return this.toResult(`Created comment on story sc-${storyPublicId}. Comment URL: ${storyComment.app_url}.`);
21889
22251
  }
22252
+ async updateStory({
22253
+ storyPublicId,
22254
+ ...updates
22255
+ }) {
22256
+ if (!storyPublicId)
22257
+ throw new Error("Story public ID is required");
22258
+ const story = await this.client.getStory(storyPublicId);
22259
+ if (!story)
22260
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}`);
22261
+ const updateParams = {};
22262
+ if (updates.name !== undefined)
22263
+ updateParams.name = updates.name;
22264
+ if (updates.description !== undefined)
22265
+ updateParams.description = updates.description;
22266
+ if (updates.type !== undefined)
22267
+ updateParams.story_type = updates.type;
22268
+ if (updates.epic !== undefined)
22269
+ updateParams.epic_id = updates.epic;
22270
+ if (updates.estimate !== undefined)
22271
+ updateParams.estimate = updates.estimate;
22272
+ if (updates.owner_ids !== undefined)
22273
+ updateParams.owner_ids = updates.owner_ids;
22274
+ if (updates.workflow_state_id !== undefined)
22275
+ updateParams.workflow_state_id = updates.workflow_state_id;
22276
+ if (updates.labels !== undefined)
22277
+ updateParams.labels = updates.labels;
22278
+ const updatedStory = await this.client.updateStory(storyPublicId, updateParams);
22279
+ return this.toResult(`Updated story sc-${storyPublicId}. Story URL: ${updatedStory.app_url}`);
22280
+ }
22281
+ async addTaskToStory({
22282
+ storyPublicId,
22283
+ taskDescription,
22284
+ taskOwnerIds
22285
+ }) {
22286
+ if (!storyPublicId)
22287
+ throw new Error("Story public ID is required");
22288
+ if (!taskDescription)
22289
+ throw new Error("Task description is required");
22290
+ const story = await this.client.getStory(storyPublicId);
22291
+ if (!story)
22292
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}`);
22293
+ if (taskOwnerIds?.length) {
22294
+ const owners = await this.client.getUserMap(taskOwnerIds);
22295
+ if (!owners)
22296
+ throw new Error(`Failed to retrieve users with IDs: ${taskOwnerIds.join(", ")}`);
22297
+ }
22298
+ const task = await this.client.addTaskToStory(storyPublicId, {
22299
+ description: taskDescription,
22300
+ ownerIds: taskOwnerIds
22301
+ });
22302
+ return this.toResult(`Created task for story sc-${storyPublicId}. Task ID: ${task.id}.`);
22303
+ }
22304
+ async updateTask({
22305
+ storyPublicId,
22306
+ taskPublicId,
22307
+ taskDescription,
22308
+ taskOwnerIds,
22309
+ isCompleted
22310
+ }) {
22311
+ if (!storyPublicId)
22312
+ throw new Error("Story public ID is required");
22313
+ if (!taskPublicId)
22314
+ throw new Error("Task public ID is required");
22315
+ const story = await this.client.getStory(storyPublicId);
22316
+ if (!story)
22317
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}`);
22318
+ const task = await this.client.getTask(storyPublicId, taskPublicId);
22319
+ if (!task)
22320
+ throw new Error(`Failed to retrieve Shortcut task with public ID: ${taskPublicId}`);
22321
+ const updatedTask = await this.client.updateTask(storyPublicId, taskPublicId, {
22322
+ description: taskDescription,
22323
+ ownerIds: taskOwnerIds,
22324
+ isCompleted
22325
+ });
22326
+ let message = `Updated task for story sc-${storyPublicId}. Task ID: ${updatedTask.id}.`;
22327
+ if (isCompleted) {
22328
+ message = `Completed task for story sc-${storyPublicId}. Task ID: ${updatedTask.id}.`;
22329
+ }
22330
+ return this.toResult(message);
22331
+ }
22332
+ async addRelationToStory({
22333
+ storyPublicId,
22334
+ relatedStoryPublicId
22335
+ }) {
22336
+ if (!storyPublicId)
22337
+ throw new Error("Story public ID is required");
22338
+ if (!relatedStoryPublicId)
22339
+ throw new Error("Related story public ID is required");
22340
+ const story = await this.client.getStory(storyPublicId);
22341
+ if (!story)
22342
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}`);
22343
+ const relatedStory = await this.client.getStory(relatedStoryPublicId);
22344
+ if (!relatedStory)
22345
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${relatedStoryPublicId}`);
22346
+ await this.client.addRelationToStory(storyPublicId, relatedStoryPublicId);
22347
+ return this.toResult(`Added relation between stories sc-${storyPublicId} and sc-${relatedStoryPublicId}.`);
22348
+ }
21890
22349
  }
21891
22350
 
21892
22351
  // src/tools/teams.ts
@@ -21930,6 +22389,7 @@ class UserTools extends BaseTools {
21930
22389
  static create(client, server) {
21931
22390
  const tools = new UserTools(client);
21932
22391
  server.tool("get-current-user", "Get the current user", async () => await tools.getCurrentUser());
22392
+ server.tool("list-members", "Get all members", async () => await tools.listMembers());
21933
22393
  return tools;
21934
22394
  }
21935
22395
  async getCurrentUser() {
@@ -21941,6 +22401,10 @@ Id: ${user2.id}
21941
22401
  Mention name: @${user2.mention_name}
21942
22402
  Full name: ${user2.name}`);
21943
22403
  }
22404
+ async listMembers() {
22405
+ const members = await this.client.listMembers();
22406
+ return this.toResult(`Found ${members.length} members, ${formatUsersList(members)}`);
22407
+ }
21944
22408
  }
21945
22409
 
21946
22410
  // src/tools/workflows.ts
package/package.json CHANGED
@@ -6,15 +6,21 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/useshortcut/mcp-server-shortcut.git"
8
8
  },
9
- "keywords": ["shortcut", "mcp", "modelcontextprotocol"],
9
+ "keywords": [
10
+ "shortcut",
11
+ "mcp",
12
+ "modelcontextprotocol"
13
+ ],
10
14
  "license": "MIT",
11
- "version": "0.4.1",
15
+ "version": "0.5.0",
12
16
  "type": "module",
13
17
  "main": "dist/index.js",
14
18
  "bin": {
15
19
  "mcp-server-shortcut": "dist/index.js"
16
20
  },
17
- "files": ["dist"],
21
+ "files": [
22
+ "dist"
23
+ ],
18
24
  "devDependencies": {
19
25
  "@biomejs/biome": "^1.9.4",
20
26
  "@types/bun": "latest",
@@ -26,9 +32,9 @@
26
32
  "typescript": "^5"
27
33
  },
28
34
  "dependencies": {
29
- "@modelcontextprotocol/sdk": "^1.6.1",
35
+ "@modelcontextprotocol/sdk": "^1.11.3",
30
36
  "@shortcut/client": "^1.1.0",
31
- "zod": "^3.24.2"
37
+ "zod": "^3.24.4"
32
38
  },
33
39
  "scripts": {
34
40
  "test": "bun test",