Repository navigation
feat(mcp): support document-scoped knowledge search #4463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -172,6 +172,44 @@ func searchTargetsFor(kbs []*types.KnowledgeBase) types.SearchTargets { | |
| return targets | ||
| } | ||
|
|
||
| // documentSearchTargets narrows an already authorized KB selection to explicit | ||
| // documents. Never leave whole-KB targets for bases with no selected documents. | ||
| func (s *Server) documentSearchTargets( | ||
| ctx context.Context, kbs []*types.KnowledgeBase, knowledgeIDs []string, | ||
| ) (types.SearchTargets, error) { | ||
| if len(knowledgeIDs) == 0 { | ||
| return nil, fmt.Errorf("knowledge_ids must contain at least one document id; " + | ||
| "omit it to search whole knowledge bases") | ||
| } | ||
| targets := make(types.SearchTargets, 0) | ||
| byKB := make(map[string]*types.SearchTarget) | ||
| seen := make(map[string]bool, len(knowledgeIDs)) | ||
| for _, id := range knowledgeIDs { | ||
| id = strings.TrimSpace(id) | ||
| if id == "" { | ||
| return nil, fmt.Errorf("knowledge_ids must not contain blank document ids") | ||
| } | ||
| if seen[id] { | ||
| continue | ||
| } | ||
| k, kb, err := s.knowledgeInKnowledgeBases(ctx, kbs, id) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| target := byKB[kb.ID] | ||
| if target == nil { | ||
| target = &types.SearchTarget{ | ||
| Type: types.SearchTargetTypeKnowledge, KnowledgeBaseID: kb.ID, TenantID: kb.TenantID, | ||
| } | ||
|
Comment on lines
+201
to
+203
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| byKB[kb.ID] = target | ||
| targets = append(targets, target) | ||
| } | ||
| target.KnowledgeIDs = append(target.KnowledgeIDs, k.ID) | ||
| seen[id] = true | ||
| } | ||
| return targets, nil | ||
| } | ||
|
|
||
| func knowledgeBaseIDs(kbs []*types.KnowledgeBase) []string { | ||
| ids := make([]string, 0, len(kbs)) | ||
| for _, kb := range kbs { | ||
|
|
@@ -213,6 +251,18 @@ func wikiScopesFor(kbs []*types.KnowledgeBase) []tools.WikiScope { | |
| // then match an allowed knowledge base and that knowledge base's tenant. | ||
| func (s *Server) knowledgeInScope( | ||
| ctx context.Context, ep *types.MCPEndpoint, knowledgeID string, | ||
| ) (*types.Knowledge, *types.KnowledgeBase, error) { | ||
| allowed, err := s.allowedKnowledgeBases(ctx, ep) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
| return s.knowledgeInKnowledgeBases(ctx, allowed, knowledgeID) | ||
| } | ||
|
|
||
| // knowledgeInKnowledgeBases applies the same document/owner checks to either | ||
| // the full endpoint scope or a narrower, already authorized KB selection. | ||
| func (s *Server) knowledgeInKnowledgeBases( | ||
| ctx context.Context, kbs []*types.KnowledgeBase, knowledgeID string, | ||
| ) (*types.Knowledge, *types.KnowledgeBase, error) { | ||
| knowledgeID = strings.TrimSpace(knowledgeID) | ||
| if knowledgeID == "" { | ||
|
|
@@ -222,13 +272,16 @@ func (s *Server) knowledgeInScope( | |
| if err != nil || k == nil { | ||
| return nil, nil, fmt.Errorf("document %q was not found", knowledgeID) | ||
| } | ||
| allowed, err := s.allowedKnowledgeBases(ctx, ep) | ||
| if err != nil { | ||
| return nil, nil, err | ||
| var kb *types.KnowledgeBase | ||
| for _, candidate := range kbs { | ||
| if candidate.ID == k.KnowledgeBaseID { | ||
| kb = candidate | ||
| break | ||
| } | ||
| } | ||
| kb := matchKnowledgeBase(allowed, k.KnowledgeBaseID) | ||
| if kb == nil || kb.TenantID != k.TenantID { | ||
| return nil, nil, fmt.Errorf("document %q is outside this endpoint's scope", knowledgeID) | ||
| return nil, nil, fmt.Errorf("document %q is outside this endpoint's scope or the selected knowledge bases", | ||
| knowledgeID) | ||
| } | ||
| return k, kb, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a client supplies a large all-valid
knowledge_idslist, this loop issues one synchronousGetKnowledgeByIDOnlydatabase lookup for every distinct ID before search. The public MCP route has no request-body cap and the new schema/handler has nomaxItemsor runtime bound, so one rate-limited call can turn thousands of IDs into thousands of serial DB queries and then an oversized backend filter, causing request stalls and database load; enforce a finite list size and/or batch the validation.Useful? React with 👍 / 👎.