diff --git a/backend/__tests__/fixtures/hostedMcp/asana-tools-list-2026-10-19.json b/backend/__tests__/fixtures/hostedMcp/asana-tools-list-2026-10-19.json new file mode 100644 index 0000000000..42d3373831 --- /dev/null +++ b/backend/__tests__/fixtures/hostedMcp/asana-tools-list-2026-10-19.json @@ -0,0 +1,4206 @@ +{ + "server": "https://mcp.asana.com/v2/mcp", + "capturedAt": "2026-10-19T07:15:23.899Z", + "scopeRequested": "default", + "grantedScope": "default identity", + "toolCount": 39, + "tools": [ + { + "name": "get_workspace_agents", + "title": "List agents in workspace", + "description": "Returns a list of AI Teammate agents (automated agents, not human users) configured in a workspace. AI Teammates are Asana-specific automation agents — they are distinct from human coworkers, teammates, or workspace members. Do NOT use this tool when the user asks about people, users, teammates, or team members; use get_users or search_objects instead. If you have a name or description to search against, use search_objects with resource_type='agent' instead. Each agent record includes GID, name, resource_type, and resource_subtype. Use to discover available AI Teammate agents before fetching full details with get_agent. Supports pagination for workspaces with many agents.", + "inputSchema": { + "type": "object", + "properties": { + "limit": { + "description": "Results per page (1-100).", + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "offset": { + "description": "Pagination offset token from a previous response.", + "type": "string" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported. Available fields: gid, resource_type, resource_subtype, name.", + "type": "string" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "List agents in workspace", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_agent", + "title": "Get agent", + "description": "Returns the full record for a single AI Teammate agent by GID. Includes name, description, behavior_guidance, workspace, and photo URLs. Use get_workspace_agents first to discover agent GIDs, then call this tool for full details.", + "inputSchema": { + "type": "object", + "properties": { + "agent_gid": { + "type": "string", + "description": "Globally unique identifier for the agent." + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported. Available fields: gid, resource_type, resource_subtype, name, description, behavior_guidance, workspace, photo.", + "type": "string" + } + }, + "required": [ + "agent_gid" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get agent", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_attachments", + "title": "Get attachments for object", + "description": "List all attachments for a project, project brief, or task. By default, returns attachment names, IDs, and URLs (download_url, permanent_url, view_url). To expose other attachment fields use opt_fields. Use for accessing files attached to Asana objects. Supports pagination for objects with many attachments.", + "inputSchema": { + "type": "object", + "properties": { + "parent": { + "type": "string", + "description": "The GID of the parent project, project_brief, or task to retrieve attachments for. Must be a GID for a project, project_brief, or task." + }, + "limit": { + "description": "Results per page (1-100)", + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "offset": { + "description": "Pagination offset token", + "type": "string" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + } + }, + "required": [ + "parent" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get attachments for object", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "create_task_from_template", + "title": "Create task from template", + "description": "List a project's task templates or create a task from one. Provide project_id alone to return all templates without creating. To create, provide either task_template_id, or template_name with project_id. When given a name, this tool searches that project's task templates and creates only when one case-insensitive exact match exists. Task instantiation starts asynchronously. Tell the user it has started and may still be in progress, then ask what they want to do next.", + "inputSchema": { + "type": "object", + "properties": { + "task_template_id": { + "description": "Globally unique identifier for the task template to instantiate. Provide this or template_name, but not both. Omit both to list every template in project_id without creating.", + "type": "string", + "minLength": 1 + }, + "template_name": { + "description": "Exact task template name to find within project_id. Matching ignores letter case. Provide this or task_template_id to create, or omit both to list every template in project_id.", + "type": "string", + "minLength": 1 + }, + "project_id": { + "description": "Globally unique identifier for the project whose task templates to search or list. Required when task_template_id is omitted.", + "type": "string", + "minLength": 1 + }, + "name": { + "description": "Name for the created task. When omitted, the task uses the template name.", + "type": "string" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Create task from template", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + } + }, + { + "name": "create_project_from_template", + "title": "Create project from template", + "description": "Create a project from a project template. If the user provides a template name instead of a GID, first use search_objects with resource_type 'project_template' and query set to that name. If search_objects requires workspace_gid and the user did not specify a workspace, call get_me to discover their workspaces. Use the only workspace automatically. If there are multiple workspaces, ask the user to choose by name; never ask them for a workspace GID. If the user wants the project in the template's team, request team.gid from search_objects and pass it as team. Omitting team does not inherit the template's team and may create a teamless project. Project instantiation starts asynchronously. Tell the user it has started and may still be in progress. Use the returned new_project GID for follow-up work, such as adding tasks or editing the project, and ask what they want to do next.", + "inputSchema": { + "type": "object", + "properties": { + "project_template_id": { + "type": "string", + "description": "Globally unique identifier for the project template. If the user provided a template name, resolve it with search_objects as described above." + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Name of the new project. Must contain a non-whitespace character." + }, + "team": { + "description": "Globally unique identifier for the new project's team. Omitting this does not inherit the template's team and may create a teamless project. Pass the template's team GID explicitly when that placement is desired.", + "type": "string" + }, + "privacy_setting": { + "description": "Privacy setting for the new project. private_to_team is deprecated; use POST /memberships to share a private project with a team after creation.", + "type": "string", + "enum": [ + "public_to_workspace", + "private_to_team", + "private" + ] + }, + "is_strict": { + "description": "When true, fail if a template date variable has no requested date. When false or omitted, use default dates for missing values.", + "type": "boolean" + }, + "requested_dates": { + "description": "Mappings from template date variables to calendar dates.", + "type": "array", + "items": { + "type": "object", + "properties": { + "gid": { + "type": "string", + "description": "Globally unique identifier for the template's date variable." + }, + "value": { + "type": "string", + "format": "date", + "pattern": "^(?:(?:\\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])))$", + "description": "Calendar date in YYYY-MM-DD format." + } + }, + "required": [ + "gid", + "value" + ] + } + }, + "requested_roles": { + "description": "Mappings from template roles to user identifiers.", + "type": "array", + "items": { + "type": "object", + "properties": { + "gid": { + "type": "string", + "description": "Globally unique identifier for the template role." + }, + "value": { + "type": "string", + "description": "User identifier to assign to the role. Can be \"me\", an email address, or a user GID." + } + }, + "required": [ + "gid", + "value" + ] + } + } + }, + "required": [ + "project_template_id", + "name" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Create project from template", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + } + }, + { + "name": "get_portfolio", + "title": "Get portfolio", + "description": "Get detailed portfolio data by ID including name, owner, and projects. Use after finding portfolio ID via search_objects. Returns complete portfolio configuration. Essential for understanding portfolio context and content.", + "inputSchema": { + "type": "object", + "properties": { + "portfolio_gid": { + "type": "string", + "description": "Globally unique identifier for the portfolio" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + } + }, + "required": [ + "portfolio_gid" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get portfolio", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_portfolios", + "title": "Get portfolios", + "description": "List portfolios in workspace owned by the current user. REQUIRES workspace parameter. Returns portfolio names and IDs for portfolios you own. Use for portfolio discovery and management. Supports pagination for workspaces with many portfolios.", + "inputSchema": { + "type": "object", + "properties": { + "limit": { + "description": "Results per page (1-100)", + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "offset": { + "description": "Pagination offset token", + "type": "string" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get portfolios", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_items_for_portfolio", + "title": "Get items for portfolio", + "description": "List projects, goals, and other items in a portfolio. Returns item names, IDs, and types. Use for portfolio content exploration and management. Supports pagination for portfolios with many items.", + "inputSchema": { + "type": "object", + "properties": { + "portfolio_gid": { + "type": "string", + "description": "Globally unique identifier for the portfolio" + }, + "limit": { + "description": "Results per page (1-100)", + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "offset": { + "description": "Pagination offset token", + "type": "string" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + } + }, + "required": [ + "portfolio_gid" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get items for portfolio", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_project", + "title": "Get project", + "description": "Get detailed project data including name, description, owner, members, and current status. Also returns task counts (num_tasks, num_incomplete_tasks, num_completed_tasks) and optionally sections. A null task_counts or sections value means the data could not be retrieved and should not be interpreted as zero or empty.", + "inputSchema": { + "type": "object", + "properties": { + "project_id": { + "type": "string", + "description": "Globally unique identifier for the project." + }, + "include_sections": { + "default": false, + "description": "Set to true to include the project's sections (up to 50) in the response.", + "type": "boolean" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + } + }, + "required": [ + "project_id" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get project", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "get_project_internal", + "title": "[Internal] Get project", + "description": "DEPRECATED; DO NOT USE. This tool must exist so that it can be invoked programmatically by widget UI, but it should not be called directly by the model.", + "inputSchema": { + "type": "object", + "properties": { + "project_id": { + "type": "string", + "description": "Globally unique identifier for the project." + }, + "include_sections": { + "default": false, + "description": "Set to true to include the project's sections (up to 50) in the response.", + "type": "boolean" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + }, + "widget_id": { + "type": "string", + "description": "ALWAYS USE A CONSTANT VALUE OF 'model-generated-widget-id' FOR THIS PARAMETER." + } + }, + "required": [ + "project_id", + "widget_id" + ], + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "[Internal] Get project", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + }, + "_meta": { + "ui": { + "visibility": [ + "app" + ] + } + } + }, + { + "name": "get_projects", + "title": "Get projects", + "description": "List projects in a workspace, optionally filtered by team. Returns project names, IDs, and task counts (num_tasks, num_incomplete_tasks, num_completed_tasks) by default. To expose other project fields, such as members or owner, use opt_fields. Prefer using search_objects with resource_type='project' for name-based searches instead of get_projects and filtering through the results. A null task_counts value means counts could not be retrieved and should not be interpreted as zero.", + "inputSchema": { + "type": "object", + "properties": { + "team": { + "description": "When passed in, filters projects on team GID", + "type": "string" + }, + "archived": { + "default": false, + "description": "Include archived projects.", + "type": "boolean" + }, + "opt_fields": { + "description": "Comma-separated list of optional fields to include in the response. Name each subfield explicitly as parent.child; wildcard and glob syntax such as parent.* is not supported.", + "type": "string" + }, + "limit": { + "default": 20, + "description": "Number of projects to return per page (1-100). Defaults to 20.", + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "offset": { + "description": "Pagination offset token from the next_page field of a previous response. If omitted, returns the first page of results.", + "type": "string" + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema" + }, + "annotations": { + "title": "Get projects", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + } + }, + { + "name": "create_project_status_update", + "title": "Create project status update", + "description": "Post a status update to a project or portfolio. Use for project health updates, milestone documentation, or blocker reporting. Returns created status with gid, parent, title, status_type, author, created_at, permalink_url. Exactly one of text or html_text must be provided (omit the other in the request).", + "inputSchema": { + "type": "object", + "properties": { + "parent": { + "type": "string", + "description": "GID of the project or portfolio to post the status update to." + }, + "title": { + "type": "string", + "description": "Status title" + }, + "text": { + "description": "Plain-text status body. Exactly one of text or html_text must be provided.", + "type": "string" + }, + "html_text": { + "description": "Exactly one of text or html_text must be provided. Allowed elements only:
, , , ,, , , - , ,
, . Do not use , ,
, or
here. Must be well-formed XML with a single root .... Only may have custom attributes; any attribute on any other element is invalid. Do not use elements outside the allowed list (400). . links: For tags specifically, to make it easier to create @-mentions through the API, we only require that you provide the GID of the object you wish to reference. If you have access to that object, the API will automatically generate the appropriate href and other attributes for you. For example, to create a link to a task with GID \"123\", you can send the tag which will then be expanded to Task Name. You can also generate a link to a task in a specific project or tag by including a data-asana-project or data-asana-tag attribute in the tag. All other attributes, as well as the contents of the tag, are ignored.\n\nTo keep the contents of your tag and make a custom vanity link, include the property data-asana-dynamic=\"false\" when setting the contents of the tag. You would send This is some custom text! and receive This is some custom text!\n\nIf you do not have access to the referenced object when you try to create a link, the API will not generate an href for you, but will instead look for an href you provide. This allows you to write back tags unmodified even if you do not have access to the resource. If you do not have access to the referenced object and no href is provided, your request will be rejected with a 400 Bad Request error. Similarly, if you provide neither a GID nor a valid href, the request will be rejected with the same error.",
+ "type": "string"
+ },
+ "color": {
+ "type": "string",
+ "enum": [
+ "green",
+ "yellow",
+ "red",
+ "blue",
+ "complete"
+ ],
+ "description": "Status color: green (on track), yellow (at risk), red (off track), blue (on hold), complete"
+ }
+ },
+ "required": [
+ "parent",
+ "title",
+ "color"
+ ],
+ "$schema": "https://json-schema.org/draft/2020-12/schema"
+ },
+ "annotations": {
+ "title": "Create project status update",
+ "readOnlyHint": false,
+ "destructiveHint": false,
+ "idempotentHint": false,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "create_project",
+ "title": "Create project",
+ "description": "Create a new project with optional sections and tasks in a single operation. Use this as the default whenever the user wants a project created or set up, including with sections and tasks. Do not choose create_project_preview unless the user explicitly asks to preview or confirm the plan before creation, or to delay creating the project until they approve. Optionally pass sections, each with a sectionName and an optional list of tasks (with name, assignee, due_on, etc.), to set up the full project structure at creation time. Tasks are nested within their sections and will be assigned to the corresponding section automatically. If a team is not provided, the new project will be associated with a default team. Returns the created project ID, permalink, and results for any sections and tasks created.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the project."
+ },
+ "team": {
+ "description": "Globally unique identifier for the team. If not provided, default team will be used.",
+ "type": "string"
+ },
+ "notes": {
+ "description": "Plain-text project description. Use html_notes for rich formatting or @-mentions.",
+ "type": "string"
+ },
+ "html_notes": {
+ "description": "HTML-formatted project description. Use notes for plain text. Allowed elements only: , , , , , , , , - , ,
, . Must be well-formed XML with a single root .... Only may have custom attributes; any attribute on any other element is invalid. Do not use elements outside the allowed list (400). . links: For tags specifically, to make it easier to create @-mentions through the API, we only require that you provide the GID of the object you wish to reference. If you have access to that object, the API will automatically generate the appropriate href and other attributes for you. For example, to create a link to a task with GID \"123\", you can send the tag which will then be expanded to Task Name. You can also generate a link to a task in a specific project or tag by including a data-asana-project or data-asana-tag attribute in the tag. All other attributes, as well as the contents of the tag, are ignored.\n\nTo keep the contents of your tag and make a custom vanity link, include the property data-asana-dynamic=\"false\" when setting the contents of the tag. You would send This is some custom text! and receive This is some custom text!\n\nIf you do not have access to the referenced object when you try to create a link, the API will not generate an href for you, but will instead look for an href you provide. This allows you to write back tags unmodified even if you do not have access to the resource. If you do not have access to the referenced object and no href is provided, your request will be rejected with a 400 Bad Request error. Similarly, if you provide neither a GID nor a valid href, the request will be rejected with the same error.",
+ "type": "string"
+ },
+ "owner": {
+ "description": "User identifier for project owner. Can be \"me\", an email, or a user GID. Prefer using \"me\" for the current user rather than calling get_me to look up the GID of the current user.",
+ "type": "string"
+ },
+ "due_on": {
+ "description": "Date on which this project is due (YYYY-MM-DD).",
+ "type": "string",
+ "format": "date",
+ "pattern": "^(?:(?:\\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])))$"
+ },
+ "start_on": {
+ "description": "Date on which this project is started (YYYY-MM-DD).",
+ "type": "string",
+ "format": "date",
+ "pattern": "^(?:(?:\\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])))$"
+ },
+ "color": {
+ "description": "Color of the project (light-green, dark-green, light-blue, dark-blue, etc).",
+ "type": "string",
+ "enum": [
+ "dark-pink",
+ "dark-green",
+ "dark-blue",
+ "dark-red",
+ "dark-teal",
+ "dark-brown",
+ "dark-orange",
+ "dark-purple",
+ "dark-warm-gray",
+ "light-pink",
+ "light-green",
+ "light-blue",
+ "light-red",
+ "light-teal",
+ "light-brown",
+ "light-orange",
+ "light-purple",
+ "light-warm-gray",
+ "none"
+ ]
+ },
+ "privacy_setting": {
+ "description": "Privacy level for the project (public_to_workspace, private_to_team, private).",
+ "type": "string",
+ "enum": [
+ "public_to_workspace",
+ "private_to_team",
+ "private"
+ ]
+ },
+ "default_view": {
+ "description": "The default view of the project (list, board, calendar, timeline).",
+ "default": "list",
+ "type": "string",
+ "enum": [
+ "list",
+ "board",
+ "calendar",
+ "timeline"
+ ]
+ },
+ "default_access_level": {
+ "description": "The default access for users or teams who join or are added as members to the project.",
+ "type": "string",
+ "enum": [
+ "admin",
+ "editor",
+ "commenter",
+ "viewer"
+ ]
+ },
+ "custom_fields": {
+ "description": "Custom field: each key is a custom field GID; each value matches that field's type in the Asana API. Use option GIDs for dropdown fields, not display names. Some tools expect a nested object; others expect one JSON string containing the same map. Examples: {\"1200456789012345\":\"1200123456789012\"} (single-select); {\"1200456789012346\":12.5} (number); {\"1200456789012347\":\"Ship by Friday\"} (text); {\"1200456789012348\":[\"1200111111111111\",\"1200222222222222\"]} (multi-select); {\"1200456789012349\":{\"date\":\"2025-06-15\"}} or {\"1200456789012349\":{\"date_time\":\"2025-06-15T17:00:00.000Z\"}} (date).",
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "number"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "object",
+ "propertyNames": {
+ "type": "string"
+ },
+ "additionalProperties": {}
+ }
+ ]
+ }
+ },
+ "members": {
+ "description": "Comma-separated user GIDs to add as project members. Example: \"123456,789012\".",
+ "type": "string"
+ },
+ "sections": {
+ "description": "Sections to create. Each section requires sectionName and may include tasks to create in that section. Use the nested task fields for descriptions, assignees, dates, followers, and custom-field values.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "sectionName": {
+ "type": "string",
+ "description": "Name of the section"
+ },
+ "tasks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the task."
+ },
+ "parent": {
+ "description": "Parent task GID to create this as a subtask.",
+ "type": "string"
+ },
+ "html_notes": {
+ "description": "HTML-formatted task description. Use notes for plain text. Allowed elements only: , , , , , , , , - , ,
, , , ,
,
,