MCP App Store

Overview

awork is the modern project management tool for creative agencies.

Tools

awork_action

ChatGPT
Run awork API actions from synchronous JavaScript. Use first-class awork.get/list/post/put/delete calls only. CRITICAL: Do NOT use async/await, Promise, fetch, imports, absolute URLs, or wrap code in (async () => ...). Use relative awork API routes discovered with find_capability. Do not use absolute URLs. Response shape: - all calls: { ok: boolean, statusCode: number, headers: Record<string, string>, body: any } - awork.list: array body - awork.get: object or primitive body - awork.post/put: usually object body - awork.delete/NoContent: body may be empty - use response.body for data Data rules: JSON request and response properties use camelCase. Date/time fields use UTC datetime strings such as 2026-05-26T12:34:56Z with second precision. Duration fields are usually seconds. UUID 01010001-0000-0000-0000-111111111111 represents actions taken by awork. Display rule: return human-readable names, links, counts, and short summaries for users. Include stable awork IDs only when they help identify records unambiguously or when another tool call needs them. AVOID unbounded large collection reads when not necessary. Do not pull huge datasets like all projects/tasks/users/documents just to filter in JS. Prefer the most specific endpoint plus server-side filterby/orderby/page/pageSize first. BUDGET RULE: runtime call budget is limited. Before .map()/.forEach() writes, estimate operation count and split into small chunks when needed instead of one giant run. Iteration: use .map() or .forEach() on response.body from GET/list results to create operations per item: const tasksResponse = awork.list('/projects/{id}/projecttasks'); tasksResponse.body.map(t => // PUT replaces the whole resource: include all existing first-level properties, // then override only the properties that should change. awork.put('/tasks/' + t.id, { ...t, // CRITICAL: always map existing properties to not lose data on PUT dueOn: '2026-04-01' }) ); Filtering: ALWAYS use server-side filterBy over in-memory filtering. Avoid n+1: f.e do not issue one delete call per task if batch endpoint exists. Before issuing a destructive action such as delete or update, run a normal read with the same final filter to confirm ids. POST /tasks/delete accepts task ID batches, and each request should contain at most 1000 IDs: let deletedCount = 0; while (true) { // GOOD: Use pagination to iterate over all items, not just first page const tasksResponse = awork.list('/projects/{id}/projecttasks', { filterby: "taskStatus/type eq 'done'", // GOOD: filter on api level page: 1, pageSize: 1000 }); const taskIds = tasksResponse.body.map(t => t.id); if (taskIds.length === 0) { break; } awork.post('/tasks/delete', { taskIds // GOOD: use batch endpoints where possible }); deletedCount += taskIds.length; } return { deletedCount }; Multiple .filter() calls can still be chained for local value shaping: tasksResponse.body.filter(a).filter(b).map(fn). Should be used with api pre-filtering if the filter is very complex or the api request performance gets significantly slower because of the filterBy. FILTER RULE: never invent filterby properties, always use the properties of the model. There might be properties which are filter only and not returned in the final response model. Nested models like project inside a task or timeentry can differ, so always check the exact shape of the model. Call find_guidance with skillIds: ['filter-syntax'] and includeContent: true for filter/order syntax and use find_capability to confirm supported fields for the exact endpoint before executing. Endpoint filterby is deterministic and not fuzzy; use GET /search for fuzzy workspace search or narrow reads for stable identifiers. Example: Filtered batch update + export: filter on the API, batch the write, and return projected rows. If the result should be used outside the immediate response, set forceDataRef=true and use continue_reading to retrieve or slice the stored result; the MCP client or agent can then write the returned rows to JSON, CSV, XLSX, or another local artifact format with its own file/artifact tools: const exportedRows = []; while (true) { // Get not billed times of a user on a project for a client, export them and set them to billed. const timeEntriesResponse = awork.list('/projects/{projectId}/timeentries', { filterby: "userId eq guid'{userId}' and startDateUtc ge datetime'2026-01-01T00:00:00Z' and startDateUtc lt datetime'2026-02-01T00:00:00Z' and isBillable eq true and isBilled eq false", orderby: 'startDateUtc asc', page: 1, pageSize: 1000 }); if (timeEntriesResponse.body.length === 0) { break; } const timeEntryIds = timeEntriesResponse.body.map(t => t.id); awork.post('/timeentries/batch/setIsBilled', { timeEntryIds, isBilled: true // Important: cannot be undone, only by admin }); timeEntriesResponse.body .map(t => ({ id: t.id, startDateUtc: t.startDateUtc, duration: t.duration, userId: t.userId, projectId: t.projectId, isBillable: t.isBillable, isBilled: true })) .forEach(row => exportedRows.push(row)); } return exportedRows; Dependencies: use property access for cross-operation refs (projectResponse.body.id resolves at execution time). DEPENDENCY RULE: verify dependent ids are non-empty before subsequent calls; do not execute routes with undefined ids. Result format: returns your final JS expression/object. If nothing is returned, a compact summary is returned. Large payloads are truncated with dataRef for continue_reading. Set forceDataRef=true when another MCP client step needs the full result. On partial failure, the result includes succeededOperationRefs/failedOperationRefs with operation indexes and extracted resourceId where available; use these IDs before retrying to avoid duplicate creates. Use find_capability first (schema-first flow) to confirm route, method and body shape. Call find_guidance with skillIds: ['tasks-execution'] before task mutations, skillIds: ['projects-lifecycle'] before project mutations, skillIds: ['search-and-entity-resolution'] before resolving ambiguous user-provided entity names, and skillIds: ['filter-syntax'] before non-trivial filters. Query parameters are passed as the second argument, e.g. { filterby, orderby, page, pageSize }. For exact count-only checks, add count: true and read response.headers['aw-totalitems']; count responses always have empty body, so do not read response.body from count requests. Keep allowNonSuccessStatusCodes=false unless you intentionally handle non-success HTTP status codes. Use continue_reading for JMESPath filtering/slicing after execution. HTML/comment rule: project and task descriptions and comments can contain HTML. Comment text can contain escaped sequences such as \u003C and \u003E; decode them when displaying formatted text. PUT rule: read first, then always send all first-level fields in body and change only intended fields. Missing first-level fields can be set to null by the API. Destructive rule: before delete/trash/remove/bulk-overwrite, run a normal read with the same final filter to confirm ids, and run a separate count-only request when an exact total is needed. Legacy array payloads like [{ method: 'POST', route: '/tasks' }] are rejected. Do not emit them. Example with allowNonSuccessStatusCodes=true: const ids = ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000002']; const found = []; for (let i = 0; i < ids.length; i += 1) { const response = awork.get('/projects/' + ids[i]); if (response.ok) { found.push(response.body); continue; } if (response.statusCode === 404) { continue; } return { error: 'Unexpected non-success status while reading project', id: ids[i], statusCode: response.statusCode, body: response.body }; } return { found }; Route discovery: always use find_capability to confirm routes before execution. Do not guess endpoint paths.

awork_action

ChatGPT
Run awork API actions from synchronous JavaScript. Use first-class awork.get/list/post/put/delete calls only. CRITICAL: Do NOT use async/await, Promise, fetch, imports, absolute URLs, or wrap code in (async () => ...). Use relative awork API routes discovered with find_capability. Do not use absolute URLs. Response shape: - all calls: { ok: boolean, statusCode: number, headers: Record<string, string>, body: any } - awork.list: array body - awork.get: object or primitive body - awork.post/put: usually object body - awork.delete/NoContent: body may be empty - use response.body for data Data rules: JSON request and response properties use camelCase. Date/time fields use UTC datetime strings such as 2026-05-26T12:34:56Z with second precision. Duration fields are usually seconds. UUID 01010001-0000-0000-0000-111111111111 represents actions taken by awork. Display rule: return human-readable names, links, counts, and short summaries for users. Include stable awork IDs only when they help identify records unambiguously or when another tool call needs them. AVOID unbounded large collection reads when not necessary. Do not pull huge datasets like all projects/tasks/users/documents just to filter in JS. Prefer the most specific endpoint plus server-side filterby/orderby/page/pageSize first. BUDGET RULE: runtime call budget is limited. Before .map()/.forEach() writes, estimate operation count and split into small chunks when needed instead of one giant run. Iteration: use .map() or .forEach() on response.body from GET/list results to create operations per item: const tasksResponse = awork.list('/projects/{id}/projecttasks'); tasksResponse.body.map(t => // PUT replaces the whole resource: include all existing first-level properties, // then override only the properties that should change. awork.put('/tasks/' + t.id, { ...t, // CRITICAL: always map existing properties to not lose data on PUT dueOn: '2026-04-01' }) ); Filtering: ALWAYS use server-side filterBy over in-memory filtering. Avoid n+1: f.e do not issue one delete call per task if batch endpoint exists. Before issuing a destructive action such as delete or update, run a normal read with the same final filter to confirm ids. POST /tasks/delete accepts task ID batches, and each request should contain at most 1000 IDs: let deletedCount = 0; while (true) { // GOOD: Use pagination to iterate over all items, not just first page const tasksResponse = awork.list('/projects/{id}/projecttasks', { filterby: "taskStatus/type eq 'done'", // GOOD: filter on api level page: 1, pageSize: 1000 }); const taskIds = tasksResponse.body.map(t => t.id); if (taskIds.length === 0) { break; } awork.post('/tasks/delete', { taskIds // GOOD: use batch endpoints where possible }); deletedCount += taskIds.length; } return { deletedCount }; Multiple .filter() calls can still be chained for local value shaping: tasksResponse.body.filter(a).filter(b).map(fn). Should be used with api pre-filtering if the filter is very complex or the api request performance gets significantly slower because of the filterBy. FILTER RULE: never invent filterby properties, always use the properties of the model. There might be properties which are filter only and not returned in the final response model. Nested models like project inside a task or timeentry can differ, so always check the exact shape of the model. Call find_guidance with skillIds: ['filter-syntax'] and includeContent: true for filter/order syntax and use find_capability to confirm supported fields for the exact endpoint before executing. Endpoint filterby is deterministic and not fuzzy; use GET /search for fuzzy workspace search or narrow reads for stable identifiers. Example: Filtered batch update + export: filter on the API, batch the write, and return projected rows. If the result should be used outside the immediate response, set forceDataRef=true and use continue_reading to retrieve or slice the stored result; the MCP client or agent can then write the returned rows to JSON, CSV, XLSX, or another local artifact format with its own file/artifact tools: con…

continue_reading

ChatGPT
Read a stored awork_action result by dataRef and optionally apply a JMESPath projection plus offset and length for precise follow-up reads. Use this when awork_action returns a large result preview and the client needs a specific slice, projection, export-ready rows, IDs for a verified follow-up action, or formatted text. Keep follow-up results concise and user-facing. Prefer names, links, counts, and relevant fields over raw full payloads. Preserve stable awork IDs when another tool call needs them. Decode escaped comment or HTML text such as \u003C and \u003E before presenting formatted comments, project descriptions, or task descriptions to users.

continue_reading

ChatGPT
Read a stored awork_action result by dataRef and optionally apply a JMESPath projection plus offset and length for precise follow-up reads. Use this when awork_action returns a large result preview and the client needs a specific slice, projection, export-ready rows, IDs for a verified follow-up action, or formatted text. Keep follow-up results concise and user-facing. Prefer names, links, counts, and relevant fields over raw full payloads. Preserve stable awork IDs when another tool call needs them. Decode escaped comment or HTML text such as \u003C and \u003E before presenting formatted comments, project descriptions, or task descriptions to users.

find_capability

ChatGPT
Search the resolved awork public OpenAPI spec with JavaScript. Use this before awork_action to discover exact routes, methods, parameters, request bodies, filter fields, and response schemas. Never guess endpoint paths, request bodies, filterby fields, or ID-bearing parameters. Entity resolution: awork entity IDs are RFC 4122 UUIDs. Never invent UUIDs. Use find_guidance with skillIds: ['search-and-entity-resolution'] and includeContent: true, then use this tool to inspect GET /search (GetSearch: searchTerm, searchTypes, top, includeClosedAndStuck) for fuzzy workspace search or inspect narrow list/read endpoints for stable filters. Endpoint filterby is deterministic, not fuzzy; confirm exact fields here before using expressions such as name eq 'Acme' or substringof('acme',tolower(name)). Prefer stable-id filters where possible. Domain guidance: call find_guidance for rules that are not obvious from OpenAPI, for example skillIds: ['filter-syntax'], skillIds: ['tasks-execution'], skillIds: ['projects-lifecycle'], skillIds: ['core-navigation'], documents, comments, time entries, reporting, or file flows.

find_capability

ChatGPT
Search the resolved awork public OpenAPI spec with JavaScript. Use this before awork_action to discover exact routes, methods, parameters, request bodies, filter fields, and response schemas. Never guess endpoint paths, request bodies, filterby fields, or ID-bearing parameters. Entity resolution: awork entity IDs are RFC 4122 UUIDs. Never invent UUIDs. Use find_guidance with skillIds: ['search-and-entity-resolution'] and includeContent: true, then use this tool to inspect GET /search (GetSearch: searchTerm, searchTypes, top, includeClosedAndStuck) for fuzzy workspace search or inspect narrow list/read endpoints for stable filters. Endpoint filterby is deterministic, not fuzzy; confirm exact fields here before using expressions such as name eq 'Acme' or substringof('acme',tolower(name)). Prefer stable-id filters where possible. Domain guidance: call find_guidance for rules that are not obvious from OpenAPI, for example skillIds: ['filter-syntax'], skillIds: ['tasks-execution'], skillIds: ['projects-lifecycle'], skillIds: ['core-navigation'], documents, comments, time entries, reporting, or file flows.

find_guidance

ChatGPT
Search or load curated awork guidance skills for MCP agents. Use this for awork domain rules and workflow context that OpenAPI cannot fully express, such as search/entity resolution, filtering syntax, safe writes, task mutations, project lifecycle, documents, comments, time entries, files, reports, and exports. For exact route/schema details still use find_capability as the OpenAPI source of truth. Recommended loads before action: - search/entity lookup: skillIds: ['search-and-entity-resolution'], includeContent: true. - core navigation and safe writes: skillIds: ['core-navigation'], includeContent: true. - non-trivial filterby/orderby: skillIds: ['filter-syntax'], includeContent: true. - task mutations: skillIds: ['tasks-execution'], includeContent: true. - project mutations: skillIds: ['projects-lifecycle'], includeContent: true. Guidance skills are static instructions only. They do not contain workspace data and are not automatically loaded into every client context.

find_guidance

ChatGPT
Search or load curated awork guidance skills for MCP agents. Use this for awork domain rules and workflow context that OpenAPI cannot fully express, such as search/entity resolution, filtering syntax, safe writes, task mutations, project lifecycle, documents, comments, time entries, files, reports, and exports. For exact route/schema details still use find_capability as the OpenAPI source of truth. Recommended loads before action: - search/entity lookup: skillIds: ['search-and-entity-resolution'], includeContent: true. - core navigation and safe writes: skillIds: ['core-navigation'], includeContent: true. - non-trivial filterby/orderby: skillIds: ['filter-syntax'], includeContent: true. - task mutations: skillIds: ['tasks-execution'], includeContent: true. - project mutations: skillIds: ['projects-lifecycle'], includeContent: true. Guidance skills are static instructions only. They do not contain workspace data and are not automatically loaded into every client context.

Capabilities

Writes

App Stats

8

Tools

ChatGPT

Platforms

Works with

ChatGPT

Data refreshed daily