-
Notifications
You must be signed in to change notification settings - Fork 1
Model Context Protocol
The OpenCrawling® Secure MCP Server (McpVectorServer) is an enterprise-grade component built on Spring AI 2.0 and the Model Context Protocol (MCP) standard (pioneered by Anthropic).
It exposes identity-aware vector search, full document retrieval, and knowledge discovery tools directly to Large Language Models (LLMs) and AI agents (such as Claude Desktop, Cursor, Antigravity, or custom agent frameworks) over standard Server-Sent Events (SSE) and HTTP WebMVC transports.
In typical RAG (Retrieval-Augmented Generation) architectures, vector databases return top-K semantic matches without evaluating the querying user's security permissions. This leads to critical data leaks when an AI agent accesses confidential files (e.g., payroll spreadsheets or strategic blueprints) on behalf of an unauthorized user.
OpenCrawling solves this with Zero-Trust Context Retrieval, combining SQL-level pre-filtering with Java-level Open Ingestion Standard (OIS) security rule evaluation before any context is delivered to the LLM client.
graph TD
subgraph LLM / MCP Client
Agent[AI Agent / LLM Client]
end
subgraph OpenCrawling Runtime
MCP[McpVectorServer]
Auth[Authority Service]
VecStore[PgVectorStore / MilvusStore]
end
subgraph Database Boundary
DB[(PostgreSQL + pgvector)]
end
Agent -->|1. secureVectorSearch query, userPrincipal, userRoles| MCP
MCP -->|2. Validate principal & map active groups| Auth
MCP -->|3. Construct dynamic FilterExpression| VecStore
VecStore -->|4. SQL Execution WHERE acl IN user_tokens| DB
DB -->|5. Candidate Vector Chunks| VecStore
VecStore -->|6. Post-Filter OIS Security Rules| MCP
MCP -->|7. Return Authorized Context JSON| Agent
-
Phase 1 — SQL-Level Pre-Filtering (Database Layer) When
secureVectorSearchorgetDocumentContentis invoked,McpVectorServeruses Spring AI'sFilterExpressionBuilderto restrict candidate rows at the database query level:SELECT * FROM vector_store WHERE embedding <=> :query_vector < :threshold AND (acl = 'public' OR acl = :user_principal OR acl = ANY(:user_roles));
-
Phase 2 — Java-Level OIS Security Evaluation (
isAccessible) For documents containing rich, multi-identity metadata under the Open Ingestion Standard (OIS)securityspecification, the server evaluates candidate chunks against granular rules:-
Explicit DENY Overrides: Any rule with
access: "deny"matching the user principal or any of their assigned roles immediately invalidates document access, overriding general READ permissions. -
Enterprise Identity Resolution: Automatically resolves
cmis-user/cmis-groupidentities andbpmn-assignee/bpmn-candidate-grouppermissions. -
Legacy ACL Fallback: If structured security metadata is absent, access defaults to evaluating flat
aclstring fields (public, exact principal match, or active role match).
-
Explicit DENY Overrides: Any rule with
The McpVectorServer registers three @McpTool components:
Performs similarity searches on vectorized enterprise documents, returning only chunks authorized for the caller's identity.
-
Signature & Parameters:
Parameter Type Required Description queryStringYes Natural language search query or keywords. userPrincipalStringYes User email or unique principal identity (e.g. john.doe@company.com).userRolesStringNo Comma-separated LDAP/OAuth roles or groups (e.g. finance,engineering).maxResultsIntegerNo Maximum number of results to return (default: 5).minScoreDoubleNo Minimum similarity threshold score from 0.0to1.0(default:0.0).dimensionsIntegerNo Target vector store dimension to query ( 384,768, or1024). -
Return DTO (
DocumentSearchResult):[ { "id": "chunk-10492", "uri": "file:///data/finance/q3_report.pdf", "content": "Q3 Revenue reached $14.2M representing a 18% YoY growth...", "metadata": { "uri": "file:///data/finance/q3_report.pdf", "acl": "finance", "mimeType": "application/pdf" }, "acl": "finance", "score": 0.892 } ]
Retrieves the complete content and metadata of a specific indexed file by URI, validating permissions prior to returning the document body.
-
Signature & Parameters:
Parameter Type Required Description documentUriStringYes Exact URI of the document (e.g. file:///docs/architecture.pdf).userPrincipalStringYes Caller principal identity. userRolesStringNo Comma-separated group memberships. dimensionsIntegerNo Vector store dimension selector ( 384,768,1024). -
Error Handling: If the document does not exist or the caller lacks authorization, the tool throws a
NoSuchElementException("Document not found or access denied.").
Enumerates all indexed knowledge documents accessible to the specified caller identity.
-
Signature & Parameters:
Parameter Type Required Description userPrincipalStringYes Caller principal identity. userRolesStringNo Comma-separated group memberships. dimensionsIntegerNo Target vector dimension store. -
Return Output Example:
[ { "id": "doc-884", "uri": "file:///shared/wiki/getting-started.md", "acl": "public", "security": null, "lastModified": "2026-07-15T14:30:00Z" } ]
OpenCrawling supports multi-tenant vector models with varying output dimensions. McpVectorServer accepts an optional dimensions parameter and routes queries to the corresponding VectorStore instance:
| Dimension | Primary Embedding Model | Default Target Store |
|---|---|---|
384 |
all-MiniLM-L6-v2 / Lightweight local models |
vectorStore384 |
768 |
nomic-embed-text / Standard enterprise models |
vectorStore768 |
1024 |
mxbai-embed-large / High-accuracy dense models |
vectorStore1024 |
null / Default |
Configured primary vector store | vectorStore |
Enable the MCP server module in your oc-runtime application settings:
spring:
ai:
vectorstore:
pgvector:
url: jdbc:postgresql://localhost:5432/opencrawling
username: opencrawling
password: opencrawling_password
dimensions: 1024
mcp:
server:
name: "opencrawling-secure-vector-mcp"
version: "1.0.0"
transport: STREAMABLE_HTTP
annotation-scanner:
enabled: trueBuild and run the decoupled MCP microservice container using Dockerfile.mcp-server:
# Build the dedicated MCP image
docker build -f docker/Dockerfile.mcp-server -t opencrawling-mcp-server .
# Run the container exposing port 8080
docker run -d \
--name opencrawling-mcp \
-p 8080:8080 \
-e OPENCRAWLING_MCP_SERVER_ENABLED=true \
-e SPRING_AI_MCP_SERVER_ANNOTATION_SCANNER_ENABLED=true \
opencrawling-mcp-serverAdd the server definition to claude_desktop_config.json:
{
"mcpServers": {
"opencrawling-secure-vector-mcp": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/client-cli",
"sse",
"http://localhost:8080/mcp/sse"
]
}
}
}Create or edit .cursor/mcp.json in your workspace or user home directory (~/.cursor/mcp.json):
{
"mcpServers": {
"opencrawling-secure-vector-mcp": {
"url": "http://localhost:8080/mcp/sse"
}
}
}Edit ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"opencrawling-mcp": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/client-cli",
"sse",
"http://localhost:8080/mcp/sse"
]
}
}
}Add the SSE server to ~/.continue/config.json:
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "sse",
"url": "http://localhost:8080/mcp/sse"
}
}
]
}
}Register the server via CLI command or ~/.gemini/antigravity-cli/mcp.json:
# Register using agy CLI command
agy mcp add opencrawling-mcp http://localhost:8080/mcp/sse --transport sseOr JSON configuration file:
{
"mcpServers": {
"opencrawling-mcp": {
"transport": "sse",
"url": "http://localhost:8080/mcp/sse"
}
}
}Programmatically call OpenCrawling tools inside Python agent applications (LangChain, LlamaIndex, or custom scripts):
import asyncio
from mcp import ClientSession
from mcp.client.sse import sse_client
async def main():
# Connect via SSE transport
async with sse_client("http://localhost:8080/mcp/sse") as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Perform a security-filtered vector search
search_response = await session.call_tool(
"secureVectorSearch",
arguments={
"query": "Kubernetes cluster architecture and deployment steps",
"userPrincipal": "devops@company.com",
"userRoles": "engineering,infrastructure",
"maxResults": 5,
"minScore": 0.3
}
)
print("Search Results:", search_response.content)
asyncio.run(main())In corporate Java services, consume the MCP server using Spring AI Starter Client (spring-ai-starter-mcp-client-webmvc):
pom.xml dependency:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webmvc</artifactId>
<version>2.0.0</version>
</dependency>application.yml:
spring:
ai:
mcp:
client:
enabled: true
name: opencrawling-client
sse:
opencrawling-server:
base-url: http://localhost:8080
sse-endpoint: /mcp/sseJava Client Service implementation:
package com.example.service;
import org.springframework.ai.mcp.client.McpClient;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class EnterpriseKnowledgeService {
private final McpClient mcpClient;
public EnterpriseKnowledgeService(McpClient mcpClient) {
this.mcpClient = mcpClient;
}
public String queryKnowledgeBase(String userEmail, String userGroups, String userQuery) {
var response = mcpClient.callTool("secureVectorSearch", Map.of(
"query", userQuery,
"userPrincipal", userEmail,
"userRoles", userGroups,
"maxResults", 5
));
return response.content().toString();
}
}Verify the MCP server endpoints manually using curl:
Step 1: Open SSE Stream Listener
curl -N -H "Accept: text/event-stream" http://localhost:8080/mcp/sseResponse includes session endpoint, e.g. event: endpoint -> /mcp/message?sessionId=abc123xyz
Step 2: Send Tool Execution JSON-RPC Request
curl -X POST "http://localhost:8080/mcp/message?sessionId=abc123xyz" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "secureVectorSearch",
"arguments": {
"query": "annual financial results",
"userPrincipal": "alice@company.com",
"userRoles": "finance"
}
}
}'Unit tests for the server implementation are located in McpVectorServerTest.java:
# Execute unit tests for MCP module
mvn test -pl oc-runtime -Dtest=McpVectorServerTest