feat: add vector-search tool with query-time in-database embeddings - #306
Open
guerinjeanmarc wants to merge 1 commit into
Open
feat: add vector-search tool with query-time in-database embeddings#306guerinjeanmarc wants to merge 1 commit into
guerinjeanmarc wants to merge 1 commit into
Conversation
Adds a vector-search MCP tool that performs semantic similarity search over
a Neo4j vector index, embedding the query in-database at call time.
- Single tool; index resolved per call via SHOW VECTOR INDEXES (optional
indexName, auto-selects the sole index otherwise).
- In-database embeddings via the GenAI plugin, provider-agnostic (OpenAI,
Azure OpenAI, Vertex AI, Bedrock Titan) through NEO4J_EMBEDDING_* env vars.
- Version-gated embedding function: ai.text.embed on Neo4j >= 2025.11,
falling back to genai.vector.encode on older versions (e.g. Aura 5.27-aura).
- Version-gated query strategy: SEARCH clause on Neo4j >= 2026.01, else
db.index.vector.queryNodes()/queryRelationships().
- Optional structured metadata filters ({property, operator, value}) applied
as a post-filter with over-fetch; operators allow-listed, identifiers escaped.
- Registered at startup gated on embedding config, so HTTP clients that fetch
tools/list once (e.g. Copilot Studio) can discover it; the version strategy
is read lazily at call time.
Security: provider, model, and API key are bound Cypher parameters (never
interpolated), never accepted as tool input or returned to clients, errors are
sanitized, and the stored embedding property is stripped from results.
Verified with go build/vet/test -race and golangci-lint (0 issues), end-to-end
against a live Aura 5.27-aura instance (genai.vector.encode fallback, OpenAI
1536-dim index), and in Microsoft Copilot Studio where it lists, executes, and
chains with read-cypher for graph traversal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
|
We have discussed this further and will not be taking this PR as it stands at the moment as we align on an approach for Tools for the official MCP server. In general we see 3 tiers of tools
In addition we want the ability to control what tools are made visible to the LLM. This avoid overloading the LLM causing poor tool selection and context consumption. Whilst we going through this process , we don't want to accept any tools for the moment as it is likely they will need reworking. Through in the other foundational work we need to do e.g get MCP 4 Aura up on VDC and we're a not in a position at the moment to give PRs the attention they deserve. So for now, this PR is on hold. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a single new MCP tool,
vector-search, that performs semantic vector similarity search over an existing Neo4j vector index. The tool embeds the user's natural-language query at query time, inside Neo4j (via the GenAI plugin), runs the similarity search, and returns the most similar nodes with their scores. It supports optional structured metadata filtering and works over both the STDIO and HTTP transports.The tool is registered only when an embedding provider is configured, so servers without embedding configuration are unaffected.
Motivation / use case
The server can already run and read Cypher, but it has no first-class way to do semantic retrieval. The driving use case is a hosted deployment (Microsoft Copilot Studio → Cloud Run → Neo4j Aura) where an agent needs to find semantically relevant nodes from natural language and then chain into
read-cypherfor graph traversal. Doing the embedding in the database means the MCP server never has to ship vectors around or hold per-request user credentials — it holds only a single server-side embedding API key.What's added
vector-searchtool (not one tool per index). The index is resolved per call viaSHOW VECTOR INDEXES: pass an optionalindexName, or omit it and the tool auto-selects when exactly one vector index exists (erroring with the list of candidates when there are several, and a clear message when there are none).NEO4J_EMBEDDING_*environment variables.{property, operator, value}, applied as a post-filter.internal/tools/vector/(spec, index resolver, embedding-config builders, query builder, handler) plus embedding-config parsing/validation ininternal/config, version detection ininternal/server/server.go, and startup registration ininternal/server/tools_register.go.manifest.jsonupdates, and changelog entries.Design decisions
Version-gated embedding function (
ai.text.embed→genai.vector.encode)ai.text.embed()was only introduced in Neo4j 2025.11. Instances on older versions — including freshly provisioned Aura Professional, which currently runs5.27-aura— do not have it, so a naive implementation fails on today's Aura.The tool detects the Neo4j version from
dbms.components()at startup and:ai.text.embedon Neo4j >= 2025.11, andgenai.vector.encodeon older versions.genai.vector.encodeuses PascalCase provider names (OpenAI,AzureOpenAI,VertexAI,Bedrock) and different config keys (flatdimensions; Azuredeploymentinstead ofmodel; VertexprojectId), so a dedicated config builder produces the fallback shape. No user action is required either way.Version-gated query strategy (
SEARCHclause →db.index.vector.queryNodes())The tool uses the
SEARCHclause on Neo4j >= 2026.01 and falls back todb.index.vector.queryNodes()/queryRelationships()on 2025.x. Both paths share the same filter/return/order/limit tail.Registered at startup (not deferred) so HTTP clients can discover it
Version-dependent behavior would normally suggest deferring registration until after version detection (as the GDS tools do). But some HTTP clients — notably Copilot Studio — import the tool list once and never re-fetch it, so a tool that appears only after the
initializehandshake is invisible to them.Instead,
vector-searchis registered at startup, gated solely on embedding configuration (known at startup in both transports). The two version gates are read lazily at call time viafunc() boolbacked byatomic.Bool, whichverifyRequirementssets once the Neo4j version is known. This keeps the tool in the initial tool list while still selecting the correct query and embedding strategy per call.Metadata filters as a post-filter with over-fetch
Filters are applied as a post-filter after the vector search. To keep result quality reasonable when filters are present, the tool over-fetches candidates (
topK × 5, capped at 1000) before filtering down totopK. Operators are validated against an allow-list (=,<>,<,<=,>,>=,IN,CONTAINS,STARTS WITH,ENDS WITH) and property names against a strict identifier pattern.Configuration
The tool is enabled only when an embedding provider is configured. The target instance must have the GenAI plugin available (standard on Aura) and at least one vector index. The embedding model must match the model used to create the stored embeddings.
NEO4J_EMBEDDING_PROVIDERopenai,azure-openai,vertexai, orbedrock-titanNEO4J_EMBEDDING_MODELNEO4J_EMBEDDING_API_KEYNEO4J_EMBEDDING_DIMENSIONSNEO4J_EMBEDDING_AZURE_RESOURCENEO4J_EMBEDDING_VERTEX_PROJECTNEO4J_EMBEDDING_VERTEX_REGIONNEO4J_EMBEDDING_VERTEX_PUBLISHERgoogle)NEO4J_EMBEDDING_AWS_ACCESS_KEY_IDNEO4J_EMBEDDING_AWS_SECRET_ACCESS_KEYNEO4J_EMBEDDING_AWS_REGIONSecurity considerations
$embedConfig) — never string-interpolated into query text.Testing
go build,go vet,go test -race, andgolangci-lint runall green (0 lint issues).5.27-aurainstance through the actual MCP server in Claude Desktop, exercising thegenai.vector.encodefallback path (OpenAItext-embedding-3-small, 1536-dim index): the tool embeds the query and returns ranked results.read-cypherfor graph traversal.Out of scope (future work)
Intentionally deferred to keep this PR focused:
get-schemaenhancement to discover vector/fulltext indexesWHERE/INfilter push-down (uses a post-filter for now)🤖 Generated with Claude Code