diff --git a/backend/__tests__/fixtures/hostedMcp/notion-tools-list-2026-10-19.json b/backend/__tests__/fixtures/hostedMcp/notion-tools-list-2026-10-19.json
new file mode 100644
index 0000000000..6df93dac39
--- /dev/null
+++ b/backend/__tests__/fixtures/hostedMcp/notion-tools-list-2026-10-19.json
@@ -0,0 +1,11724 @@
+{
+ "server": "https://mcp.notion.com/mcp",
+ "capturedAt": "2026-10-19T07:15:35.472Z",
+ "scopeRequested": null,
+ "tokenShape": {
+ "keys": [
+ "access_token",
+ "expires_in",
+ "issuer",
+ "refresh_token",
+ "scope",
+ "token_type"
+ ],
+ "tokenType": "bearer",
+ "grantedScope": "default",
+ "expiresIn": 28800,
+ "hasRefreshToken": true
+ },
+ "toolCount": 65,
+ "tools": [
+ {
+ "name": "notion-search",
+ "title": "Search Notion",
+ "description": "Search Notion with short, specific keywords, or look up a user by name or email. Use this tool for every content search. This workspace uses keyword search, so prefer distinctive words from page titles or content over full questions. AI search, which answers natural-language questions and also searches connected sources, requires a Business or Enterprise plan with Notion AI.\n\nStart with a non-empty query and omit optional parameters unless needed. Do not add filters or sorting just to fill in defaults. Business or Enterprise is required for filters.edited_by_user_ids, filters.last_edited_date_range, filters.title_only=true, filters.content_status, more than one distinct teamspace across teamspace_id and filters.teamspace_ids, and sort other than relevance. On other plans, those options are dropped with a notice in the result, and results may be broader. Keep filters nested inside \"filters\". Use page_size, not limit.\n\nFor user lookup, set query_type=\"user\" and provide a name or email. A document about a person is a content search, not a user lookup.\n\n\n{\"query\":\"Q3 roadmap\"}\n\n\n{\"query\":\"alex@example.com\",\"query_type\":\"user\"}\n",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "description": "Short, specific keywords for Notion content search. For user search, enter a name or email address. Provide a non-empty query unless intentionally browsing with filters or a non-relevance sort."
+ },
+ "query_type": {
+ "description": "Specify type of the query as either \"internal\" or \"user\". Always include this input if performing \"user\" search.",
+ "type": "string",
+ "enum": [
+ "internal",
+ "user"
+ ]
+ },
+ "data_source_url": {
+ "description": "Optionally restrict keyword search to a data source URL returned in a tag.",
+ "type": "string"
+ },
+ "page_url": {
+ "description": "Optionally restrict keyword search to a page and its descendants. Accepts a Notion page URL or ID.",
+ "type": "string"
+ },
+ "teamspace_id": {
+ "description": "Optionally, provide the ID of a teamspace to restrict search results to. This will perform a search over content within the specified teamspace only. Accepts the teamspace ID (UUIDv4) with or without dashes.",
+ "type": "string"
+ },
+ "filters": {
+ "description": "Optional exact filters for Notion workspace search. Omit unless required by the request. Keep filter fields nested here; do not send them at the top level. Some filters require Business access.",
+ "type": "object",
+ "properties": {
+ "created_date_range": {
+ "description": "Optional filter to only produce search results created within the specified date range.",
+ "type": "object",
+ "properties": {
+ "start_date": {
+ "description": "The start date of the date range as an ISO 8601 date string, if any.",
+ "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])))$"
+ },
+ "end_date": {
+ "description": "The end date of the date range as an ISO 8601 date string, if any.",
+ "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])))$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "created_by_user_ids": {
+ "description": "Optional filter to only produce search results created by the Notion users that have the specified user IDs. Use \"me\" for the connected user.",
+ "maxItems": 100,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "edited_by_user_ids": {
+ "description": "Optional filter to only produce search results edited by the Notion users that have the specified user IDs. Use \"me\" for the connected user. Available on the Business plan.",
+ "maxItems": 100,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "last_edited_date_range": {
+ "description": "Optional filter to only produce search results last edited within the specified date range. Available on the Business plan.",
+ "type": "object",
+ "properties": {
+ "start_date": {
+ "description": "The start date of the date range as an ISO 8601 date string, if any.",
+ "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])))$"
+ },
+ "end_date": {
+ "description": "The end date of the date range as an ISO 8601 date string, if any.",
+ "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])))$"
+ }
+ },
+ "additionalProperties": false
+ },
+ "teamspace_ids": {
+ "description": "Optional filter to only produce search results inside one of the specified teamspaces. Selecting more than one teamspace is available on the Business plan; use teamspace_id for one teamspace on other plans.",
+ "maxItems": 100,
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "title_only": {
+ "description": "When true, match the query only against page and database titles instead of page content. Available on the Business plan.",
+ "type": "boolean"
+ },
+ "content_status": {
+ "description": "Which pages to include by status. Omit for the default live pages. Supplying this field, even with the default value, requires Business access.",
+ "type": "string",
+ "enum": [
+ "all_with_archived",
+ "all_without_archived",
+ "verified_only",
+ "archived_only"
+ ]
+ }
+ },
+ "additionalProperties": {}
+ },
+ "sort": {
+ "description": "Result ordering for Notion workspace search. Omit for the default \"relevance\" ordering. \"last_edited\" and \"created\" require Business access.",
+ "type": "string",
+ "enum": [
+ "relevance",
+ "last_edited",
+ "created"
+ ]
+ },
+ "page_size": {
+ "description": "Maximum number of results to return (default 10). Lower values reduce response size.",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 50
+ },
+ "max_highlight_length": {
+ "description": "Maximum character length for result highlights (default 200). Set to 0 to omit highlights entirely.",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 500
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": {}
+ },
+ "annotations": {
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "notion-memory-search",
+ "title": "Search personal memories",
+ "description": "Search memories that the user's connected agents saved in earlier sessions: context, preferences, corrections, and decisions. Returns the best matches, not every memory. To list, count, or filter memories, fetch id \"memory\" and run query-data-sources on its data source. The first time you use memory in a conversation, whether searching or saving, read notion://docs/memory once.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500,
+ "description": "Words or a short phrase to find in personal memories."
+ },
+ "limit": {
+ "description": "Maximum number of memories to return. Defaults to 10 and cannot exceed 50.",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 50
+ }
+ },
+ "required": [
+ "query"
+ ],
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": false
+ },
+ "annotations": {
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "notion-get-tool-access",
+ "title": "Get tool access",
+ "description": "Get current tool availability, parameter restrictions, and available upgrade links for this Notion connection. This lookup is optional: tools apply access limits themselves and report them in their results as notices or errors with upgrade links. Use it when the user asks which Notion tools or features are available, or after a result reports restricted access or an upgrade requirement. Call with {} for the full access map, or pass tool_names to narrow the result. Unknown names and tools not exposed to this connection are omitted. The map may report AI search access under ai_search; use search for every content search, which runs AI search when it is available. Tool availability and restricted_parameters are independent. This tool does not grant access or change the workspace.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "tool_names": {
+ "description": "Optional RunTool names to include, such as search and ai_search. Omit to return all tools visible to this connection. Unknown or unexposed names are omitted. An empty list returns an empty map.",
+ "maxItems": 100,
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ }
+ }
+ },
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": false
+ },
+ "annotations": {
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "notion-fetch",
+ "title": "Fetch Notion entities",
+ "description": "Retrieves details about a Notion entity (page, database, data source, or saved database view) by URL or ID.\n\nProvide a URL or ID in `id`. Make multiple calls for multiple entities.\n\nFor pages, use path, verification, edit time, and truncation metadata to assess the source. Treat recency as context, not proof. Report material uncertainty when sources conflict.\n\nPages use enhanced Markdown format. For the complete specification, read the MCP resource `notion://docs/enhanced-markdown-spec` through your MCP client's resource-reading interface, or call the Notion \"fetch\" tool with this URI if your client does not support reading MCP resources. Do NOT pass this URI to any other URL-fetching tool.\n\nPass a `notion://docs/*` URI as `id` to read that MCP documentation resource.\n\nDatabases return data sources in `` tags. Fetch the collection:// URL for its schema and use it with update_data_source or query_data_sources. Multi-source databases return more than one data source.\n\nSaved views return filters, sorts, and display settings. To query their rows, use query_data_sources with `mode: \"view\"`.\n\nA null cover or icon means none; an omitted one means unavailable, not absent. Keep a page's Markdown icon attribute for edit round trips. File URLs expire; re-fetch to refresh them. Uploaded media blocks use stable `notion-file-block://` sources; keep them when editing or copying Markdown. Get download URLs with get_file_download_urls, or set `include_file_urls` to true for a `references` map. Before relying on page content, check `truncated`, `unknown_block_count`, and `unknown_block_ids` for omitted subtrees.\n\nSet `include_discussions` to true for discussion counts, markers, previews, and `discussion://` URLs that correlate with get_comments.\n\nUse get_tool_access to check tool availability, parameter restrictions, and upgrade links for this connection.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The ID or URL of the Notion page, database, or data source to fetch. Supports notion.so URLs, Notion Sites URLs (*.notion.site), raw UUIDs, and data source URLs (collection://...). Pass \"memory\" to fetch the connected user's About page and memory database in this workspace without creating them. Pass a notion://docs/* URI to read that documentation resource. Also accepts an explicit saved view URL (view://...) from a database response. If a database block ID returns a validation error, use the collection:// URL from that error. Example: notion://docs/enhanced-markdown-spec."
+ },
+ "include_transcript": {
+ "description": "Whether to include meeting note transcripts. Defaults to false. When true, full transcripts are included; when false, a placeholder with the meeting note URL is shown instead.",
+ "type": "boolean"
+ },
+ "include_discussions": {
+ "description": "Whether to include discussion/comment indicators in the page output. When true, adds a summary with discussion count, preview snippets, and discussion:// URLs. Use with the get_comments tool to retrieve full discussion content. Defaults to false.",
+ "type": "boolean"
+ },
+ "include_file_urls": {
+ "description": "Whether to return signed download URLs for uploaded files. Defaults to false. When false, page Markdown cites uploaded files by stable notion-file-block:// references and the response has no references map; call get_file_download_urls with the references when you need those files' contents. When true, the response's references map gives the signed URL for each reference.",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": {}
+ },
+ "annotations": {
+ "readOnlyHint": true,
+ "destructiveHint": false,
+ "idempotentHint": true,
+ "openWorldHint": false
+ }
+ },
+ {
+ "name": "notion-create-attachment",
+ "title": "Create a Notion attachment",
+ "description": "Create an attachment and upload it to Notion.\n\nProvide exactly one source:\n- content for small UTF-8 text artifacts such as HTML, Markdown, plain text, CSV, JSON, XML, CSS, YAML, TSV, calendar, GPX, or SVG files.\n- source_url for a direct, publicly reachable HTTPS file URL.\n- source_file_id for a file this exact integration already uploaded. When create_file_upload is available, use it for local files so the upload has the same owner.\n\n\nFor content and source_url, filename must use a supported extension and content_type must agree with it. Omit content_type to infer it. source_file_id takes neither because the stored upload carries both. For larger files, redirects, or authenticated downloads, upload through the Notion File Upload API with this integration's token and pass source_file_id.\n\nThe response includes a markdown_source value. To place the uploaded file on a page, pass that source to create-pages or update-page. To attach it to a comment, include suggested_markdown on a separate line in create-comment markdown. Unattached uploads remain temporary and are deleted once they expire: content and source_url open a fresh one-hour window, while source_file_id keeps the window that opened when the file was first uploaded, so place that source promptly and upload the file again if it has already expired. \"HTML\", \"HTML block\", \"HTML artifact\", and \"HTML embed\" all mean an HTML file placed with