Install
$ agentstack add mcp-montygovernance-montycat-dart ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
💙 Montycat for Dart & Flutter — The AI-Native NoSQL Database with Semantic Search for RAG & Agents
Abolish the two-database stack.
The official Dart & Flutter SDK for Montycat — a self-hosted NoSQL + vector database with AI semantic search forged into the core, built for RAG and AI-agent memory. One Rust engine, not a sprawl of services. Your hardware. Your data. Your meaning.
[](https://pub.dev/packages/montycat) [](https://pub.dev/packages/montycat/score) [](https://hub.docker.com/r/montygovernance/montycat) [](https://pub.dev/packages/montycat) [](https://github.com/MontyGovernance/montycat_dart/blob/master/LICENSE)
// Search your data by MEANING — no external APIs, no separate vector database.
// (already ON by default in the montycat-semantic server edition)
final hits = await production.searchValues(query: 'Show all Bluetooth devices', limit: [0, 5]);
// → [{__key__: 123..., __score__: 0.78, __value__: { name: 'Wireless Headphones' }}]
> ### 🧩 All-in-one. AI-native. Zero external dependencies. > The vector-embedding engine runs inside the database — no separate vector DB, no embedding API, no API keys, no sidecar service. One engine, one binary, your hardware.
What is Montycat?
For a generation we were told the price of intelligence was two systems: a database for your records, and a separate vector store — with its per-query bill — for their meaning. Montycat rejects that tax. It is a self-hosted NoSQL + vector database: one Rust-powered engine with semantic search built in, so RAG, AI-agent memory, and vector search live where your data already lives. No cloud lock-in. No ops headache. Decentralized by nature, ultra-fast, and natively async.
Think of it as an open-source, self-hosted alternative to Pinecone, Weaviate, Chroma, Qdrant, and Redis — a vector database and a NoSQL store in a single engine — that feels native to Dart & Flutter across mobile, web, desktop, and server-side Dart.
🌐 More Than a Database — a Living Data Mesh
Montycat is not storage you query. It is a structured, reactive, high-performance data mesh you converse with — and every part of it belongs to you:
- Hybrid Engine — memory-speed in-memory operations and persistent durability in one place.
- Domain-Oriented Keyspaces — each keyspace is an independently owned data product, not a shared table.
- Reactive Core — native subscriptions for live apps and analytics.
- Rust-Powered — memory-safe, zero-cost abstractions, ultra-low latency.
- One Clean API — real-time subscriptions, hybrid storage, and structured data behind a single async surface, built for Dart & Flutter.
✨ Why Dart & Flutter Developers Defect to Montycat
- ⚡ No More Waiting — forget slow queries, bloated drivers, and ORM hell.
- 🗂️ Domain-Oriented Data — each keyspace is a product you own and control.
- 📡 Live & Reactive — dashboards, notifications, analytics: real-time is effortless.
- 🛡️ Safe & Future-Proof — a Rust engine, TLS, and memory-safe guarantees.
- 🌐 Cross-Platform — Flutter mobile, web, desktop, and server-side Dart. No hacks.
🔍 Example Use Cases
- RAG pipelines & semantic retrieval for LLM-powered Dart/Flutter apps
- On-device AI agent / chatbot memory that survives restarts
- Semantic search & recommendations — match intent, not keywords
- Real-time dashboards, notifications, and live collaborative apps
- Offline-first Flutter cache backed by a real engine
- Data products in a decentralized Mesh architecture
🚀 Get the Engine (30 seconds)
The client talks to a Montycat server. Fastest way — Docker, with AI semantic search built in:
docker run -d --name montycat \
-p 21210:21210 -p 21211:21211 \
-e MONTYCAT_SUPEROWNER="admin" \
-e MONTYCAT_PASSWORD="change-me" \
-v montycat_data:/var/lib/.montycat \
montygovernance/montycat:semantic
Prefer the lean edition without the embedding engine? Use the latest tag. Prebuilt packages (apt, macOS, Windows) at https://montygovernance.com.
📦 Installation
Add montycat to your pubspec.yaml:
dependencies:
montycat: ^1.2.4
Then fetch packages:
dart pub get
# or for Flutter
flutter pub get
Quick Start
import 'dart:async';
import 'package:montycat/montycat.dart'
show
Engine,
KeyspaceInMemory,
KeyspacePersistent,
Timestamp,
Schema,
FieldType;
class Customer extends Schema {
Customer(super.kwargs);
static String get schemaName => 'Customer';
static Map get schemaMetadata => {
'name': FieldType(String),
'age': FieldType(int, nullable: true),
'email': FieldType(String, nullable: true),
};
@override
Map metadata() => schemaMetadata;
}
class Orders extends Schema {
Orders(super.kwargs);
static String get schemaName => 'Orders';
static Map get schemaMetadata => {
'date': FieldType(Timestamp),
'quantity': FieldType(int),
'customer': FieldType(String),
};
@override
Map metadata() => schemaMetadata;
}
Future main() async {
Engine engine = Engine(
host: '127.0.0.1',
port: 21210,
username: 'USER',
password: '12345',
store: 'Company',
);
KeyspaceInMemory customers = KeyspaceInMemory(keyspace: 'customers');
KeyspacePersistent production = KeyspacePersistent(keyspace: 'production');
customers.connectEngine(engine);
production.connectEngine(engine);
final customersCreated = await customers.createKeyspace();
final productionCreated = await production.createKeyspace();
print("Keyspaces created: $customersCreated, $productionCreated");
var customer = Customer({'name': 'Alice Smith', 'age': 28, 'email': null});
var custInsert = await customers.insertValue(value: customer.serialize());
print(custInsert);
//{status: true, payload: 29095364578528255816148465894650046051, error: null}
var custFetched = await customers.getValue(
key: '30748150595091665781806646557034343545',
);
print(custFetched);
//{status: true, payload: {name: Alice Smith, age: 28, email: alice.smith@example.com}, error: null}
var custUpdate = await customers.updateValue(
key: '30748150595091665781806646557034343545',
updates: {'age': 29},
);
print(custUpdate);
//{status: true, payload: null, error: null}
var custDelete = await customers.deleteKey(
key: '30748150595091665781806646557034343545',
);
print(custDelete);
//{status: true, payload: null, error: null}
var custVerifyKeys = await customers.getKeys();
print(custVerifyKeys);
//{status: true, payload: [], error: null}
var order = Orders({
'date': Timestamp(timestamp: DateTime.now().toUtc().toString()),
'quantity': 3,
'customer': 'Name',
});
var prodInsert = await production.insertValue(value: order.serialize());
print(prodInsert);
//{status: true, payload: 30442970696809394303186116932586352271, error: null}
var prodFetched = await production.getValue(
key: '30648912591862065620656997781578274575',
);
print(prodFetched);
//{status: true, payload: {date: 2025-10-05T12:34:56.789Z, quantity: 3, customer: Name}, error: null}
var prodUpdate = await production.updateValue(
key: '30648912591862065620656997781578274575',
updates: {'quantity': 10},
);
print(prodUpdate);
//{status: true, payload: null, error: null}
var prodLookup = await production.lookupValuesWhere(
searchCriteria: {'quantity': 10, 'date': Timestamp(after: '2025-10-01')},
keyIncluded: true,
schema: Orders.schemaName,
);
print(prodLookup);
//{status: true, payload: [{__key__: 30442970696809394303186116932586352271, __value__: {date: 2025-10-05T12:34:56.789Z, quantity: 10, customer: Name}}], error: null}
}
🧠 Ranked Search — Semantic, BM25 Keyword, and Hybrid
Montycat provides semantic vector search, persistent BM25 keyword search, and hybrid ranking in one database. Use lookup* for exact structured matching; use searchKeys or searchValues for relevance-ranked retrieval.
- 🔎 Semantic / vector search — kNN similarity over on-device embeddings, not brittle keyword matches.
- 🤖 Built for AI — RAG, semantic retrieval, AI agents, recommendations, dedup, clustering.
- 🔒 Private & free — embeddings never leave your machine. No OpenAI/Cohere bill, no data egress.
- ⚡ One system, not two — your data and its vectors live in the same database. No sync jobs, no drift, no second service to run.
- 🚀 Zero setup — no index tuning, no pipeline:
enableSemanticSearch()and you're ranking by meaning.
> ⚠️ Requires the semantic edition of the server — nothing to compile. Semantic > search runs an embedded ONNX vector-embedding engine that ships only in the > montycat-semantic edition; the default lean montycat server does not include it. > Get it the way that suits you — pull the Docker image > (montygovernance/montycat:semantic), download the prebuilt package, or install > montycat-semantic from the apt repository. The Dart client API is identical either > way; just point it at a semantic-edition server (semantic search is enabled by default > there, using the bge-small model).
The switch is DB-wide and already on in the semantic edition; every keyspace is embedded in the background as data is written (the embedding model is downloaded on demand).
// Semantic search is ON by default in the montycat-semantic edition — just search.
// Rank stored items by meaning — two flavors:
// getValues → each hit is {__key__, __score__, __value__}
// getKeys → each hit is {__key__, __score__} (lighter; fetch a page later with getBulk)
final hits = await production.searchValues(
query: 'bulk order of blue widgets',
mode: SearchMode.hybrid,
limit: [0, 5],
);
final keys = await production.searchKeys(
query: 'blue widgets',
mode: SearchMode.keyword,
limit: [0, 5],
);
// Optionally drop weak matches by cosine similarity (range [-1, 1]).
final strong = await production.searchKeys(
query: 'bulk order of blue widgets',
mode: SearchMode.semantic,
limit: [0, 5],
minScore: 0.35,
);
// Read back the actual model and backfill state.
final semantic = await engine.getSemanticStatus(
store: 'catalog',
keyspace: 'products',
);
final productsStatus = semantic.keyspace('catalog', 'products');
// After globally re-enabling semantic search, wait until
// semantic.reloading is false before searching retained indexes.
// semantic.indexing reports live and backfill queue depths.
// Enable an unenrolled keyspace with an explicit model.
await engine.enableSemanticSearch(
model: SemanticModel.bgeBase,
store: 'catalog',
keyspace: 'products',
);
// Changing an enrolled keyspace's model is destructive. This atomic operation
// drops its old vectors and starts a complete backfill.
await engine.reembedSemanticSearch(
model: SemanticModel.bgeBase,
store: 'catalog',
keyspace: 'products',
);
// turn it off (vectors are kept so re-enabling resumes instantly;
// pass dropVectors: true to also clear stored vectors)
await engine.disableSemanticSearch();
Search modes and metadata filters
semantic ranks by vector similarity, keyword uses BM25, and hybrid combines both rankings with reciprocal-rank fusion. Optional filters are an exact hard pre-filter and do not contribute to relevance.
__score__ is cosine similarity in semantic mode, raw BM25 relevance in keyword mode, and a normalized [0, 1] RRF score in hybrid mode. Keyword scores have no fixed upper bound, so compare scores only within the same query and search mode. A hybrid score near 1.0 means strong agreement between both rankings; a top result found by only one branch is around 0.5. minScore filters the final selected mode score before pagination. In hybrid mode this means the fused RRF score; keyword-only fallback hits are filtered too.
final matchingKeys = await production.searchKeys(
query: 'astronomy and outer space',
mode: SearchMode.hybrid,
filters: {'category': 'space'},
limit: [0, 5],
minScore: 0.35,
);
final matchingValues = await production.searchValues(
query: 'astronomy and outer space',
mode: SearchMode.hybrid,
filters: {'category': 'space'},
limit: [0, 5],
);
// key hits: {__key__, __score__}
// value hits: {__key__, __score__, __value__}
Bring your own vectors
If you already have embeddings from a batch pipeline or vector store, first enroll the keyspace for externally generated vectors. External profiles support 1–4,096 dimensions for OpenAI-style 1,536d pipelines, Pinecone/Qdrant/Milvus migrations, and image or multimodal vectors:
await items.createKeyspace(semantic: false);
await engine.enablePrecomputedVectorSearch(
store: 'app', keyspace: 'items', dimensions: 1536,
embeddingSpace: 'text-embedding-3-small:v1',
);
embeddingSpace is a descriptive name for the vectors' model/configuration; it does not invoke or validate that model. Then supply vectors directly and the server skips embedding. Needs a Montycat Semantic server 1.3.0 or newer.
// Writing: pass `vector` alongside the value.
await production.insertValue(
value: {'text': 'The Voyager probes left the heliosphere.'},
vector: myEmbedding, // List
);
// Bulk: paired with bulkValues by position.
await production.insertBulk(
bulkValues: [doc1, doc2],
vectors: [embedding1, embedding2],
);
// Searching: pass a query vector; the query string may be empty.
final hits = await production.searchValues(
query: '',
mode: SearchMode.semantic,
vector: myQueryEmbedding,
limit: [0, 10],
);
vector is also accepted by insertCustomKeyValue and updateValue, and updateBulk takes vectors for numeric keys plus customVectors for custom keys. All four semanticSearch* methods accept a query vector.
Serialized Schema values can be passed directly to updateBulk. Their schema entry is transported as request metadata rather than stored as a document field, while nested timestamps metadata remains intact. Every value in one bulk update must use the same schema.
Embedding-space compatibility is required. Every supplied record vector and query vector must be produced by the model enrolled for that keyspace, including the same model revision, preprocessing, pooling, and normalization. Matching the dimension alone is not enough: an auto-enrolled BGE-small keyspace accepts only BGE-small-compatible 384d vectors. To use vectors from another model, create the keyspace with semantic auto-enrollment disabled and enroll a matching external profile first. The server validates dimensions before anything reaches the index, but it cannot prove that two equal-length vectors came from the same embedding space. A vector you supplied will not be overwritten by background embedding; a later ordinary write to that item clears the protection and re-embeds from its text, which is when re-embedding is what you want.
Mixing is fine: items with supplied vectors and items the server embeds can live in one keyspace as long as every vector comes from the same model.
📨 Response Shape
Every call resolves to the same envelope, so there is one thing to check everywhere:
// {status: true, payload: , error: null}
// {status: false, payload: null, error: 'Governance permission denied: ...'}
final res = await customers.insertValue(value: customer.serialize());
if (res['status'] == true) print(res['payload']);
payload is null for commands that only acknowledge, the new key for inserts, and a list for lookups and semantic searches. Keys are u128 and always arrive as strings — never parse one into int, which silently truncates above 2^63. Invalid arguments throw ArgumentError before anything touches the network; server-side failures come back in error with `status: fa
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: MontyGovernance
- Source: MontyGovernance/montycat_dart
- License: MIT
- Homepage: https://montygovernance.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.