epics-search
Search and filter epics in Shortcut by criteria like name, state, owner, due dates, and completion status to manage project workflows.
Instructions
Find Shortcut epics.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| nextPageToken | No | If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters. | |
| id | No | Find only epics with the specified public ID | |
| name | No | Find only epics matching the specified name | |
| description | No | Find only epics matching the specified description | |
| state | No | Find only epics matching the specified state | |
| objective | No | Find only epics matching the specified objective | |
| owner | No | Find entities where the owner match the specified user. This must either be the user's mention name or the keyword "me" for the current user. | |
| requester | No | Find entities where the requester match the specified user. This must either be the user's mention name or the keyword "me" for the current user. | |
| team | No | Find only epics matching the specified team. Should be a team's mention name. | |
| comment | No | Find only epics matching the specified comment | |
| isUnstarted | No | Find only entities that are unstarted when true, or only entities that are not unstarted when false. | |
| isStarted | No | Find only entities that are started when true, or only entities that are not started when false. | |
| isDone | No | Find only entities that are completed when true, or only entities that are not completed when false. | |
| isArchived | No | Find only entities that are archived when true, or only entities that are not archived when false. | |
| isOverdue | No | Find only entities that are overdue when true, or only entities that are not overdue when false. | |
| hasOwner | No | Find only entities that have an owner when true, or only entities that do not have an owner when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner. | |
| hasComment | No | Find only entities that have a comment when true, or only entities that do not have a comment when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner. | |
| hasDeadline | No | Find only entities that have a deadline when true, or only entities that do not have a deadline when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner. | |
| hasLabel | No | Find only entities that have a label when true, or only entities that do not have a label when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner. | |
| created | No | 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"). | |
| updated | No | 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"). | |
| completed | No | 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"). | |
| due | No | 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"). |
Implementation Reference
- src/tools/epics.ts:94-107 (handler)The core handler function for the 'epics-search' tool. Builds search query, calls Shortcut client API, handles results and pagination, formats output with related entities.async searchEpics(params: QueryParams, nextToken?: string) { const currentUser = await this.client.getCurrentUser(); const query = await buildSearchQuery(params, currentUser); const { epics, total, next_page_token } = await this.client.searchEpics(query, nextToken); if (!epics) throw new Error(`Failed to search for epics matching your query: "${query}"`); if (!epics.length) return this.toResult(`Result: No epics found.`); return this.toResult( `Result (${epics.length} shown of ${total} total epics found):`, await this.entitiesWithRelatedEntities(epics, "epics"), next_page_token, ); }
- src/tools/epics.ts:29-77 (registration)Registers the 'epics-search' tool on the CustomMcpServer using addToolWithReadAccess, including description, input schema, and wrapper to the searchEpics handler.server.addToolWithReadAccess( "epics-search", "Find Shortcut epics.", { nextPageToken: z .string() .optional() .describe( "If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters.", ), id: z.number().optional().describe("Find only epics with the specified public ID"), name: z.string().optional().describe("Find only epics matching the specified name"), description: z .string() .optional() .describe("Find only epics matching the specified description"), state: z .enum(["unstarted", "started", "done"]) .optional() .describe("Find only epics matching the specified state"), objective: z .number() .optional() .describe("Find only epics matching the specified objective"), owner: user("owner"), requester: user("requester"), team: z .string() .optional() .describe( "Find only epics matching the specified team. Should be a team's mention name.", ), comment: z.string().optional().describe("Find only epics matching the specified comment"), isUnstarted: is("unstarted"), isStarted: is("started"), isDone: is("completed"), isArchived: is("archived").default(false), isOverdue: is("overdue"), hasOwner: has("an owner"), hasComment: has("a comment"), hasDeadline: has("a deadline"), hasLabel: has("a label"), created: date(), updated: date(), completed: date(), due: date(), }, async ({ nextPageToken, ...params }) => await tools.searchEpics(params, nextPageToken), );
- src/server.ts:49-49 (registration)Top-level call to EpicTools.create which initializes the EpicTools instance and registers all epic tools (including 'epics-search') on the MCP server.EpicTools.create(client, server);
- src/tools/epics.ts:32-76 (schema)Input schema (Zod object) for the 'epics-search' tool parameters, defining all searchable fields and filters.{ nextPageToken: z .string() .optional() .describe( "If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters.", ), id: z.number().optional().describe("Find only epics with the specified public ID"), name: z.string().optional().describe("Find only epics matching the specified name"), description: z .string() .optional() .describe("Find only epics matching the specified description"), state: z .enum(["unstarted", "started", "done"]) .optional() .describe("Find only epics matching the specified state"), objective: z .number() .optional() .describe("Find only epics matching the specified objective"), owner: user("owner"), requester: user("requester"), team: z .string() .optional() .describe( "Find only epics matching the specified team. Should be a team's mention name.", ), comment: z.string().optional().describe("Find only epics matching the specified comment"), isUnstarted: is("unstarted"), isStarted: is("started"), isDone: is("completed"), isArchived: is("archived").default(false), isOverdue: is("overdue"), hasOwner: has("an owner"), hasComment: has("a comment"), hasDeadline: has("a deadline"), hasLabel: has("a label"), created: date(), updated: date(), completed: date(), due: date(), }, async ({ nextPageToken, ...params }) => await tools.searchEpics(params, nextPageToken),
- src/tools/utils/search.ts:19-36 (helper)Utility function to build the Shortcut search query string from the tool input parameters, handling key mapping, user mentions, booleans, and quoting.export const buildSearchQuery = async (params: QueryParams, currentUser: MemberInfo | null) => { const query = Object.entries(params) .map(([key, value]) => { const q = getKey(key); if (key === "owner" || key === "requester") { if (value === "me") return `${q}:${currentUser?.mention_name || value}`; return `${q}:${String(value || "").replace(/^@/, "")}`; } if (typeof value === "boolean") return value ? q : `!${q}`; if (typeof value === "number") return `${q}:${value}`; if (typeof value === "string" && value.includes(" ")) return `${q}:"${value}"`; return `${q}:${value}`; }) .join(" "); return query; };