# Montycat Dart

> AI-native NoSQL + vector database with built-in semantic search for RAG & AI agents — the Dart & Flutter client. Self-hosted Pinecone alternative.

- **Type:** MCP server
- **Install:** `agentstack add mcp-montygovernance-montycat-dart`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [MontyGovernance](https://agentstack.voostack.com/s/montygovernance)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [MontyGovernance](https://github.com/MontyGovernance)
- **Source:** https://github.com/MontyGovernance/montycat_dart
- **Website:** https://montygovernance.com

## Install

```sh
agentstack add mcp-montygovernance-montycat-dart
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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](https://montygovernance.com) — 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)

```dart
// 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:

```bash
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`:

```yaml
dependencies:
  montycat: ^1.2.4
```

Then fetch packages:

```bash
dart pub get
# or for Flutter
flutter pub get
```

## Quick Start

```dart
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).

```dart
// 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.

```dart
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:

```dart
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.

```dart
// 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:

```dart
// {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](https://github.com/MontyGovernance)
- **Source:** [MontyGovernance/montycat_dart](https://github.com/MontyGovernance/montycat_dart)
- **License:** MIT
- **Homepage:** https://montygovernance.com

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-montygovernance-montycat-dart
- Seller: https://agentstack.voostack.com/s/montygovernance
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
