lookup_documents_by_name
Search for documents in IBM FileNet Content Manager using keywords from document names. Returns matching documents with confidence scores for selection.
Instructions
:param keywords: Up to 3 words from the user's message that might contain the document's name. Avoid using very common words such as "and", "or", "the", etc. :param class_symbolic_name: If specified, a specific document class to look in for matching documents. The root Document class is used by default. Specify a class only if the user indicates that the documents should belong to a specific class. Use the determine_class tool to lookup the class symbolic name based on the user's message.
:returns: A list of matching documents, or a ToolError if no matches are found or there is some other problem. Each match is a DocumentMatch object with information about the document including its name and a confidence score.
Description: This tool will execute a search to lookup documents by name. A list of the most likely documents matching the keywords is returned. Use this list to select the appropriate document based on the user's message.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | ||
| class_symbolic_name | No |
Implementation Reference
- Primary handler for the 'lookup_documents_by_name' tool. Decorated with @mcp.tool for registration. Performs GraphQL search on document names using LIKE conditions for keywords, filters by version status, scores matches using fuzzy token similarity, and returns up to MAX_SEARCH_RESULTS DocumentMatch objects sorted by score.@mcp.tool( name="lookup_documents_by_name", ) async def lookup_documents_by_name( keywords: List[str], class_symbolic_name: Optional[str] = None, ) -> Union[List[DocumentMatch], ToolError]: """ :param keywords: Up to 3 words from the user's message that might contain the document's name. Avoid using very common words such as "and", "or", "the", etc. :param class_symbolic_name: If specified, a specific document class to look in for matching documents. The root Document class is used by default. Specify a class only if the user indicates that the documents should belong to a specific class. Use the determine_class tool to lookup the class symbolic name based on the user's message. :returns: A list of matching documents, or a ToolError if no matches are found or there is some other problem. Each match is a DocumentMatch object with information about the document including its name and a confidence score. Description: This tool will execute a search to lookup documents by name. A list of the most likely documents matching the keywords is returned. Use this list to select the appropriate document based on the user's message. """ method_name = "lookup_documents_by_name" if not class_symbolic_name: class_symbolic_name = DEFAULT_DOCUMENT_CLASS class_data = get_class_metadata_tool( graphql_client, class_symbolic_name=class_symbolic_name, metadata_cache=metadata_cache, ) # Check if we got an error instead of class data if isinstance(class_data, ToolError): return class_data logger.debug( msg=f"class_data.name_property_symbolic_name = {class_data.name_property_symbolic_name}" ) if class_data.name_property_symbolic_name is None: return ToolError( message=f"Class {class_symbolic_name} does not have a name property", ) keyword_conditions: list[str] = [] for keyword in keywords: # query_conditions.append( # f"LOWER({class_data.name_property_symbolic_name}) LIKE %{keyword.lower()}%" # ) keyword_conditions.append( "LOWER(" + class_data.name_property_symbolic_name + ") LIKE '%" + keyword.lower() + "%'" ) keyword_conditions_string: str = " OR ".join(keyword_conditions) logger.debug("keyword_conditions_string: str = " + keyword_conditions_string) # Include condition to search only against commonly retrieved documents -- released if any; in-process version if any; initial reservation where_statement: str = ( f"(VersionStatus = {VERSION_STATUS_RELEASED} OR (VersionStatus = {VERSION_STATUS_IN_PROCESS} AND MajorVersionNumber = {INITIAL_MAJOR_VERSION}) OR (VersionStatus = {VERSION_STATUS_RESERVATION} AND MajorVersionNumber = {INITIAL_MAJOR_VERSION} AND MinorVersionNumber = {INITIAL_MINOR_VERSION})) AND (" + keyword_conditions_string + ")" ) logger.debug("where_statement: str = " + where_statement) query_text = """ query documentsByNameSearch( $object_store_name: String!, $class_name: String!, $where_statement: String!) { documents( repositoryIdentifier: $object_store_name, from: $class_name, where: $where_statement ) { documents { className id name majorVersionNumber minorVersionNumber versionStatus } } }""" var = { "object_store_name": graphql_client.object_store, "where_statement": where_statement, "class_name": class_symbolic_name, } docs: list[dict] try: response = await graphql_client.execute_async( query=query_text, variables=var ) if "errors" in response: logger.error("GraphQL error: %s", response["errors"]) return ToolError(message=f"{method_name} failed: {response['errors']}") docs = response["data"]["documents"]["documents"] except Exception as e: return ToolError( message=f"Error executing search: {str(e)}", ) logger.debug(f"Search for documents returned {len(docs)} documents") matches: list[Any] = [] for doc in docs: match_score: float = score_document(doc, keywords) logger.debug( msg=f"document {doc['name']} matched with score of {match_score}" ) if match_score > 0: matches.append((doc, match_score)) # Sort matches by score (highest first) matches.sort(key=lambda x: x[1], reverse=True) # if we found matches, return up to the maximum matches max_results = MAX_SEARCH_RESULTS if matches: doc_matches: list[DocumentMatch] = [] # Convert all available matches (up to max) to DocumentMatch objects for doc, score in matches[:max_results]: doc_name = doc["name"] logger.debug( f"Document {doc_name} selected with matched score of {score}" ) match: DocumentMatch = DocumentMatch( id=doc["id"], name=doc["name"], class_name=doc["className"], score=score, ) doc_matches.append(match) return doc_matches return ToolError( message=f"No document matching keywords {keywords} found in the class '{class_symbolic_name}'", suggestions=[ "Try using different keywords", "Check if the keywords are spelled correctly", "Ask the user for the specific document they want to use", ], )
- Pydantic model defining the structure of each returned document match, used as output schema for the tool.class DocumentMatch(BaseModel): """Information about a document that matches a search.""" class_name: str = Field( default="Document", description="Class identifier for the document" ) id: str = Field(description="The id of the document") name: Optional[str] = Field(default=None, description="The name of the document") score: float = Field( description="The match score, higher values indicate better matches" )
- src/cs_mcp_server/mcp_server_main.py:223-223 (registration)Call to register_search_tools function during core server tool registration, which defines and registers the lookup_documents_by_name tool.register_search_tools(mcp, graphql_client, metadata_cache)
- src/cs_mcp_server/mcp_server_main.py:239-239 (registration)Call to register_search_tools function during full server tool registration, which defines and registers the lookup_documents_by_name tool.register_search_tools(mcp, graphql_client, metadata_cache)
- Helper function to compute match score for a document against keywords, delegating to score_name for fuzzy matching logic.def score_document(doc: dict, keywords: List[str]) -> float: """ Advanced scoring method that uses tokenization and fuzzy matching to find the best document match. This scoring algorithm works by: 1. Tokenizing text (breaking CamelCase and snake_case into individual words) 2. Performing fuzzy matching between keywords and tokens 3. Giving bonuses for exact matches and for matching multiple keywords :param doc: The document to score. A dictionary returned from the graphql search. :param keywords: The keywords to match against :return: A score indicating how well the document matches the keywords """ # Convert all text to lowercase for case-insensitive matching name = doc["name"].lower() match_score: float = score_name(name, keywords) return match_score