Skip to content

Commit 44016cd

Browse files
authored
feat(search): browse Lucid folders and Notion pages (#8579)
* feat(search): browse Lucid folders and Notion pages * fix(search): preserve discovery query boundaries
1 parent ba0c8c7 commit 44016cd

17 files changed

Lines changed: 1066 additions & 144 deletions

‎apps/docs/content/docs/search/lucid.mdx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ Sim uses [Lucid’s official read-only MCP server](https://help.lucid.co/hc/en-u
1111

1212
## Search
1313

14-
Search requires terms, even when filtering by date. Results are ranked for relevance and may not match the title literally. Start with a short document-title query, such as `deployment architecture`. Select `lucidchart` or `lucidspark` to narrow the product, or search both. Read a result to inspect its diagram or board structure.
14+
Title search requires terms, even when filtering by date. Results are ranked for relevance and may not match the title literally. Start with a short document-title query, such as `deployment architecture`. Select `lucidchart` or `lucidspark` to narrow the product, or search both. Read a result to inspect its diagram or board structure.
1515

1616
To find text inside a known document, use its UUID or Lucid URL as the native query’s `project` and enter a literal phrase such as `API Gateway`. This matches shape labels and sticky-note text, case-insensitively. It does not search notes, tags, links or comments.
1717

1818
Title queries allow up to 400 characters; document-scoped text queries allow 200. Boolean and field operators are unsupported. Document search has no continuation and verifies at most 10 candidates. Dates use modification time; filtering and sorting the returned candidates cannot establish the newest or oldest document across the entire account.
1919

20+
## Browse documents
21+
22+
Ask to browse your Lucid folders when you do not have a title or topic. The assistant can list the root folder or a selected child folder without guessing search terms. Each request returns one page of direct children; child folders are navigation references, not document content. Follow the continuation for more items in the same folder, or select a child folder to explore it.
23+
24+
Browsing uses the same personal readonly connection. It does not recursively scan the account, follow shortcuts, or guarantee the newest or oldest document across folders. Date filters and sorting apply to the current page’s document metadata.
25+
2026
## Diagram content
2127

2228
Reads preserve Lucid’s structured pages, nodes, connections and properties, with links back to the source. Large responses can be read in successive windows of the same document version. Sim rejects incomplete or changed documents rather than treating a partial graph as complete.

‎apps/docs/content/docs/search/notion.mdx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ Use short, distinctive keywords such as `launch rollback` or a concise question
1313

1414
Sim checks the connection's current tool access before searching. When available, it uses Notion AI search; otherwise it uses the advertised keyword-search route. Workspace plans and enabled MCP tools determine availability. Notion can return results from connected applications, but Sim's Notion provider returns only Notion pages and databases. Search Slack, Gmail, or Drive through their own providers for those sources.
1515

16-
The assistant can scope a query to a Notion page URL or ID when the server advertises page-scoped search. A server without that option reports the limitation. For date filters or sorting, Sim fetches up to 10 candidate pages and uses their explicit modification timestamps. They are not exhaustive date-range searches; date-only requests require search terms. Sim never substitutes the search time for a missing page date.
16+
The assistant can scope a query to a Notion page URL or ID when the server advertises page-scoped search. A server without that option reports the limitation. When the connection’s plan and tool schema allow it, Sim sends modification-date filters and newest sorting to Notion. It then checks exact timestamps from up to 10 current page reads. Other connections use local date filtering for keyword searches; date-only searches report the missing plan capability. These bounded results do not establish exhaustive date coverage or the oldest page in the workspace. Sim never substitutes the search time for a missing page date.
17+
18+
## Browse pages
19+
20+
Ask for your private, shared, favorite, or recently viewed pages to browse without inventing keywords. Private and shared lists reflect the corresponding sidebar sections; they do not enumerate the whole workspace. Recently viewed pages are ranked by visits and frequency, not modification time. Follow the returned continuation to see more entries in the same list, and read a page for its contents.
21+
22+
List availability is checked against the connected account’s current tools and access. Browsing keeps the same personal OAuth connection and requires no additional credentials.
1723

1824
## Coverage and reads
1925

2026
Search returns a bounded, ranked selection. Pagination is used only when both the advertised tool and response support it. If results are capped, external results are dropped, or Notion reports plan restrictions, Sim marks the coverage as partial and suggests refining the query.
2127

2228
Reads fetch the exact page or database through the same member connection. Large pages can omit subtrees; Sim preserves that limitation in the returned content. Open the original page when the result says its content is incomplete.
2329

24-
Search is limited to a fixed read-only allowlist: tool-access inspection, search, AI search, and fetch. It cannot create or edit Notion pages. The server URL is fixed to Notion's official endpoint, and every call is validated against the current advertised tool schema.
30+
Search is limited to a fixed read-only allowlist: tool-access inspection, search, AI search, sidebar lists, and fetch. It cannot create or edit Notion pages. The server URL is fixed to Notion's official endpoint, and every call is validated against the current advertised tool schema.
2531

2632
See [Notion MCP setup](https://developers.notion.com/guides/mcp/get-started-with-mcp) and the [supported tools and plan behavior](https://developers.notion.com/guides/mcp/mcp-supported-tools).

‎apps/sim/lib/api/contracts/mothership-assistant-tools.ts‎

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import { LIVE_SEARCH_PROVIDER_IDS } from '@/lib/sim-search/live/provider-catalog
44
export const liveSearchProviderSchema = z.enum(LIVE_SEARCH_PROVIDER_IDS)
55
export type LiveSearchProvider = z.output<typeof liveSearchProviderSchema>
66

7-
export const SEARCH_TERMS_REQUIRED = {
8-
notion: 'Notion requires search terms. Add keywords or a concise question.',
9-
lucid: 'Lucid requires search terms. Add document-title keywords or a literal shape-text query.',
10-
} as const
11-
127
/**
138
* Native queries one call may send to the same provider account. Alternatives run as separate
149
* provider searches and fuse into one ranking, so the bound keeps a call within the provider's
@@ -54,6 +49,12 @@ export const nativeSearchQuerySchema = z
5449
accountId: z.string().min(1).max(200).optional(),
5550
kind: nativeSearchKindSchema.optional(),
5651
project: z.string().min(1).max(300).optional(),
52+
browse: z
53+
.enum(['folder', 'private', 'shared', 'favorites', 'recent'])
54+
.optional()
55+
.describe(
56+
'Queryless discovery: Lucid folder lists one folder page (omit project for root; otherwise use a returned numeric folder ID). Notion private/shared list sidebar pages, favorites lists pinned pages, recent lists recently viewed pages, not recently modified pages. These lists are not an exhaustive workspace inventory. Follow the returned cursor with the same account, browse mode, project, filters and topK.'
57+
),
5758
cursor: z.string().max(4000).optional(),
5859
termClauses: z.array(z.string().max(500)).max(10).optional(),
5960
modifiers: z.string().max(1000).optional(),
@@ -72,11 +73,32 @@ export const nativeSearchQuerySchema = z
7273
: `${input.provider} does not support kind selection.`,
7374
})
7475
}
75-
if ((input.provider === 'notion' || input.provider === 'lucid') && !input.query)
76+
if (
77+
input.browse &&
78+
(input.query ||
79+
input.modifiers ||
80+
input.keywordOnly ||
81+
input.termClauses?.length ||
82+
(input.browse === 'folder' ? input.provider !== 'lucid' : input.provider !== 'notion') ||
83+
(input.provider === 'notion' && input.project))
84+
)
85+
context.addIssue({
86+
code: 'custom',
87+
path: ['browse'],
88+
message:
89+
'Browse requires an empty query and a supported provider mode; Notion lists cannot be scoped to a page.',
90+
})
91+
if (input.browse === 'folder' && input.project && !/^[1-9]\d{0,14}$/.test(input.project))
92+
context.addIssue({
93+
code: 'custom',
94+
path: ['project'],
95+
message: 'Lucid folder browsing requires a numeric folder ID returned by the provider.',
96+
})
97+
if (input.provider === 'lucid' && !input.query && !input.browse)
7698
context.addIssue({
7799
code: 'custom',
78100
path: ['query'],
79-
message: SEARCH_TERMS_REQUIRED[input.provider],
101+
message: 'Lucid requires title keywords or explicit browse: folder.',
80102
})
81103
})
82104
export type NativeSearchQuery = z.output<typeof nativeSearchQuerySchema>
@@ -113,7 +135,9 @@ export const nativeSearchQueriesSchema = z
113135
addIssue('Duplicate native query.')
114136
else if (
115137
hasSearchKinds(query.provider) &&
116-
earlier.some((previous) => !previous.kind || !query.kind)
138+
earlier.some(
139+
(previous) => !previous.browse && !query.browse && (!previous.kind || !query.kind)
140+
)
117141
)
118142
addIssue(
119143
'A GitHub, GitLab, HubSpot, Lucid, Google Meet, or Zoom query without a kind already searches its default kinds; give each query on this account a kind.'
@@ -134,6 +158,10 @@ export const liveSearchAccountStatusSchema = z.object({
134158
status: z.enum(['ok', 'partial', 'reconnect', 'rate_limited', 'unavailable', 'timeout']),
135159
message: z.string().optional(),
136160
nextCursor: z.string().optional(),
161+
folders: z
162+
.array(z.object({ id: z.string(), name: z.string() }))
163+
.max(10)
164+
.optional(),
137165
retryAfterSeconds: z.number().optional(),
138166
})
139167
export type LiveSearchAccountStatus = z.output<typeof liveSearchAccountStatusSchema>
@@ -198,15 +226,15 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
198226
nativeQueries: nativeSearchQueriesSchema
199227
.optional()
200228
.describe(
201-
`Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound or sortBy newest/oldest; Notion and Lucid always require search terms. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple queries on one account, each need a kind, which may repeat. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid searches titles with no search continuation; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.`
229+
`Live search only: queries in a provider's own language (Drive q, Gmail operators, JQL, CQL, GitHub qualifiers, Slack RTS, plain Linear/Fireflies/HubSpot/Lucid/Zoom terms, bounded local Google Meet text matching, Granola natural-language questions, Notion keywords or AI questions when available). Blank queries require a date bound, sortBy newest/oldest, or explicit browse mode. Lucid browse folder lists root or a numeric folder project; Notion browse private/shared/favorites/recent lists sidebar pages. Recent means viewed, not modified. Up to ${MAX_NATIVE_QUERIES_PER_ACCOUNT} per account run separately and merge; one GitHub, GitLab, or HubSpot query without a kind searches GitHub issues (plus code when the query has no date bound or boolean operators, as its status message says), GitLab issues, merge requests, and code, or every HubSpot CRM kind; other collections, and multiple content queries on one account, each need a kind, which may repeat; explicit browse queries may select distinct folders or sidebar sections without a kind. HubSpot kinds are contacts, companies, deals, and tickets; Lucid kinds are lucidchart and lucidspark. Google Meet kinds are transcript and smart_notes (note metadata and Docs link only); it searches bounded recent conference artifacts with 30-day retention. Zoom kind is meeting and searches past occurrences; read for transcripts and separately labeled summaries. Use Drive for saved Meet note bodies and older transcripts; Drive dates mean file modification time. HubSpot, Lucid, Zoom and Meet reject ownership filters. Lucid title search has no continuation; folder browsing is paginated and returns child folders in account coverage; project can scope a literal shape-text query to one known document UUID or Lucid URL. Read for structured diagram evidence. Dates and sorting cover only retrieved candidates, not globally newest/oldest matches. Write queries from the returned live guidance and account IDs; each account status names the queryIndex its cursor belongs to. Omit for simple cross-provider terms.`
202230
),
203231
query: z
204232
.string()
205233
.trim()
206234
.max(2000)
207235
.default('')
208236
.describe(
209-
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported; Notion requires search terms.'
237+
'Search terms, without dates already supplied as filters. May be empty for a live listing with a date bound or sortBy newest or oldest where supported, or use an explicit native browse mode. Notion date-only search depends on plan capabilities.'
210238
),
211239
topK: z
212240
.number()
@@ -227,7 +255,11 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
227255
input.sortBy === 'newest' ||
228256
input.sortBy === 'oldest'
229257
)
230-
if (!input.query && !input.nativeQueries?.some((query) => query.query) && !bounded)
258+
if (
259+
!input.query &&
260+
!input.nativeQueries?.some((query) => query.query || query.browse) &&
261+
!bounded
262+
)
231263
context.addIssue({
232264
code: 'custom',
233265
path: ['query'],
@@ -243,7 +275,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
243275
path: ['endDate'],
244276
message: 'endDate must be after startDate.',
245277
})
246-
if (input.nativeQueries?.some((query) => !query.query) && !bounded)
278+
if (input.nativeQueries?.some((query) => !query.query && !query.browse) && !bounded)
247279
context.addIssue({
248280
code: 'custom',
249281
path: ['nativeQueries'],

0 commit comments

Comments
 (0)