Docs Packages
@procedures/mongo
MongoDB client wrapper with client procedures for local or RPC access. Provides a comprehensive set of procedures for database operations, collection management, document CRUD, indexing, and aggregation pipeline support.
Overview#
client-mongo bridges MongoDB operations into the client procedure ecosystem, enabling both:
- Local usage: Direct MongoDB connection with native driver
- RPC usage: Remote procedure calls via
client.call()
The package follows the client procedure pattern, auto-registering 16 procedures organized into 4 categories:
- Database:
ping,info - Collections:
list,create,drop,stats - Documents:
find,get,insert,update,delete,count,aggregate - Indexes:
list,create,drop
Architecture#
graph TB
subgraph "Client Ecosystem"
Client["@procedures/client"]
Registry["Procedure Registry"]
end
subgraph "client-mongo Package"
Register["register.ts<br/>(Auto-registers procedures)"]
Connection["connection.ts<br/>(Connection Management)"]
subgraph "Procedure Categories"
DB["Database Procedures<br/>ping, info"]
Collections["Collection Procedures<br/>list, create, drop, stats"]
Documents["Document Procedures<br/>find, get, insert, update, delete, count, aggregate"]
Indexes["Index Procedures<br/>list, create, drop"]
end
Types["types.ts<br/>(Type Definitions)"]
ProcTypes["procedures.types.ts<br/>(Typed Interface)"]
end
subgraph "MongoDB Driver"
MongoDB["mongodb npm package"]
MongoClient["MongoClient"]
Db["Db Instance"]
end
subgraph "External Systems"
MongoServer["MongoDB Server<br/>(local or remote)"]
end
Register --> Client
Register --> Registry
DB --> Connection
Collections --> Connection
Documents --> Connection
Indexes --> Connection
Connection --> MongoClient
MongoClient --> Db
Db --> MongoServer
Types -.-> DB
Types -.-> Collections
Types -.-> Documents
Types -.-> Indexes
ProcTypes -.provides typing.-> Client
style Register fill:#e1f5ff
style Connection fill:#fff4e1
style Types fill:#f0f0f0Package Dependency Flow#
graph LR
App["Your Application"]
ClientMongo["@procedures/mongo"]
Client["@procedures/client"]
MongoDB["mongodb"]
App --> ClientMongo
ClientMongo --> Client
ClientMongo --> MongoDB
App -.client.call.-> Client
Client -.executes.-> ClientMongo
ClientMongo -.queries.-> MongoDBConnection Management Flow#
sequenceDiagram
participant App
participant Connection
participant MongoClient
participant MongoDB
App->>Connection: connect({ uri, database })
Connection->>MongoClient: new MongoClient(uri)
MongoClient->>MongoDB: Connect
MongoDB-->>MongoClient: Connected
Connection->>Connection: setDefaultConnection()
Connection-->>App: MongoConnection
App->>Connection: getDb()
Connection-->>App: Db instance
App->>Connection: disconnect()
Connection->>MongoClient: close()
MongoClient->>MongoDB: DisconnectInstallation#
npm install @procedures/mongo
Peer Dependencies#
{
"@procedures/client": "github:mark1russell7/client#main",
"mongodb": "^7.0.0"
}
Configuration#
Environment Variables#
# MongoDB connection URI (optional, defaults to mongodb://localhost:27017)
MONGODB_URI=mongodb://localhost:27017
# Default database name (optional, defaults to "test")
MONGODB_DATABASE=myapp
Programmatic Configuration#
import { connect } from "@procedures/mongo";
// Using environment variables
await connect();
// With explicit options
await connect({
uri: "mongodb://localhost:27017",
database: "myapp",
clientOptions: {
// Any MongoDB client options
maxPoolSize: 10,
minPoolSize: 2,
}
});
The first call connects#
A host does not have to call connect(). When there is no open connection, the first procedure call connects with the options of configure(), or with the environment variables. Concurrent first calls share one attempt.
import { configure } from "@procedures/mongo";
configure({ uri: "mongodb://localhost:27017", database: "myapp" });
The collection and the database of a call#
Each procedure reads collection and database from its input. An MCP tool call can set them. The call metadata is the fallback, for the clients that set it.
await client.exec(["mongo", "documents", "count"], { collection: "users", query: { active: true } });
API Reference#
Connection Management#
connect(options?): Promise<MongoConnection>#
Create and connect to MongoDB. Sets the default connection used by all procedures.
interface MongoConnectionOptions {
uri?: string; // Default: MONGODB_URI or mongodb://localhost:27017
database?: string; // Default: MONGODB_DATABASE or "test"
clientOptions?: MongoClientOptions;
}
interface MongoConnection {
getDb(): Db;
getClient(): MongoClient;
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
}
Example:
const conn = await connect({
uri: "mongodb://localhost:27017",
database: "myapp"
});
disconnect(): Promise<void>#
Disconnect the default connection.
await disconnect();
getDb(): Db#
Get the database instance from the default connection.
const db = getDb();
const collection = db.collection("users");
getClient(): MongoClient#
Get the MongoClient from the default connection.
const client = getClient();
getDefaultConnection(): MongoConnection#
Get the default connection instance. Throws if not connected.
setDefaultConnection(conn: MongoConnection): void#
Set the default connection (advanced usage).
hasDefaultConnection(): boolean#
Check if a default connection exists.
Registered Procedures#
All procedures are automatically registered when the package is imported. They can be called via client.call() or used directly.
Database Procedures#
mongo.database.ping#
Test database connectivity and measure latency.
// Via RPC
const result = await client.exec(
["mongo", "database", "ping"],
{ timeout: 5000 }
);
// Direct import
import { pingProcedure } from "@procedures/mongo";
Input:
interface PingInput {
timeout?: number; // Optional timeout in milliseconds
}
Output:
interface PingOutput {
latencyMs: number; // Response time in milliseconds
ok: boolean; // Server status
}
mongo.database.info#
Get database information and statistics.
const result = await client.exec(
["mongo", "database", "info"],
{ includeStats: true }
);
Input:
interface InfoInput {
includeStats?: boolean;
}
Output:
interface DatabaseInfo {
name: string; // Database name
collections: number; // Number of collections
views: number; // Number of views
sizeOnDisk: number; // Size on disk in bytes
empty: boolean; // Whether database is empty
}
Collection Procedures#
mongo.collections.list#
List all collections in the database.
const result = await client.exec(
["mongo", "collections", "list"],
{ filter: "user" }
);
Input:
interface ListCollectionsInput {
filter?: string; // Filter by name pattern
}
Output:
interface ListCollectionsOutput {
collections: string[]; // Collection names
}
mongo.collections.create#
Create a new collection.
const result = await client.exec(
["mongo", "collections", "create"],
{
name: "logs",
options: {
capped: true,
size: 10485760, // 10MB
max: 1000
}
}
);
Input:
interface CreateCollectionInput {
name: string;
options?: CollectionOptions;
}
interface CollectionOptions {
capped?: boolean;
size?: number; // Max size in bytes (for capped)
max?: number; // Max documents (for capped)
validator?: Document; // JSON Schema validator
}
Output:
interface CreateCollectionOutput {
created: boolean;
name: string;
}
mongo.collections.drop#
Drop a collection.
const result = await client.exec(
["mongo", "collections", "drop"],
{ name: "old_collection" }
);
Input:
interface DropCollectionInput {
name: string;
}
Output:
interface DropCollectionOutput {
dropped: boolean;
}
mongo.collections.stats#
Get collection statistics.
const result = await client.exec(
["mongo", "collections", "stats"],
{},
{ metadata: { collection: "users" } }
);
Input:
interface StatsInput {
// Collection from metadata
}
Output:
interface CollectionStats {
count: number; // Number of documents
size: number; // Size of documents in bytes
avgObjSize: number; // Average document size
storageSize: number; // Total storage size
nindexes: number; // Number of indexes
totalIndexSize: number; // Total index size
}
Document Procedures#
mongo.documents.find#
Find documents with pagination, filtering, sorting, and projection.
const result = await client.exec(
["mongo", "documents", "find"],
{
query: { status: "active", age: { $gte: 18 } },
projection: { name: 1, email: 1 },
sort: { createdAt: -1 },
page: 1,
limit: 20
},
{ metadata: { collection: "users" } }
);
Input:
interface FindInput {
query?: DocumentQuery; // MongoDB filter
projection?: Record<string, 0 | 1>; // Field projection
sort?: SortSpec; // Sort specification
page?: number; // Page number (1-indexed)
limit?: number; // Items per page (max 100)
}
type DocumentQuery = Filter<Document>;
type SortSpec = Record<string, 1 | -1>;
Output:
interface FindOutput {
documents: MongoDocument[];
pagination: PaginationOutput;
}
interface PaginationOutput {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
}
mongo.documents.get#
Get a single document by ID.
const result = await client.exec(
["mongo", "documents", "get"],
{ id: "507f1f77bcf86cd799439011" },
{ metadata: { collection: "users" } }
);
Input:
interface GetInput {
id: string; // Document ID (converted to ObjectId)
}
Output:
interface GetOutput {
document: MongoDocument | null;
}
mongo.documents.insert#
Insert one or more documents.
// Insert single document
const result = await client.exec(
["mongo", "documents", "insert"],
{
documents: {
name: "John Doe",
email: "john@example.com",
createdAt: new Date()
}
},
{ metadata: { collection: "users" } }
);
// Insert multiple documents
const result = await client.exec(
["mongo", "documents", "insert"],
{
documents: [
{ name: "Alice" },
{ name: "Bob" }
]
},
{ metadata: { collection: "users" } }
);
Input:
interface InsertInput {
documents: MongoDocument | MongoDocument[];
}
Output:
interface InsertOutput {
insertedCount: number;
insertedIds: string[];
}
mongo.documents.update#
Update one document by id, or the documents that match a filter.
A write needs exactly one of id or filter. An unknown field is an error: query is the field of find and count, not of update. An empty filter matches every document, so it needs confirm: true.
// Update one document by ID
const result = await client.exec(
["mongo", "documents", "update"],
{
collection: "users",
id: "...",
update: { $set: { status: "inactive" } },
upsert: false
}
);
// Update multiple documents
const result = await client.exec(
["mongo", "documents", "update"],
{
collection: "users",
filter: { status: "pending" },
update: { $set: { status: "active" } },
multi: true
}
);
Input:
interface UpdateInput {
collection?: string; // Or metadata.collection
database?: string; // Or metadata.database
id?: string; // Exactly one of id and filter
idType?: "auto" | "objectId" | "string";
filter?: DocumentQuery;
update: DocumentUpdate;
multi?: boolean; // Update all matching documents
upsert?: boolean; // Insert if not found
confirm?: boolean; // Required for an empty filter
}
type DocumentUpdate = UpdateFilter<Document>;
An upsert by id in the "auto" mode updates the document with that ID in either form. When no document matches, the new document gets the ObjectId form of the ID.
Output:
interface UpdateOutput {
acknowledged: boolean;
matchedCount: number;
modifiedCount: number;
upsertedId: string | null;
upsertedCount: number;
}
mongo.documents.delete#
Delete one document by id, or the documents that match a filter. The rules of update apply: exactly one target, no unknown fields, and confirm: true for an empty filter.
// Delete one document by ID
const result = await client.exec(
["mongo", "documents", "delete"],
{ collection: "users", id: "..." }
);
// Delete multiple documents
const result = await client.exec(
["mongo", "documents", "delete"],
{
collection: "users",
filter: { status: "inactive" },
multi: true
}
);
Input:
interface DeleteInput {
collection?: string; // Or metadata.collection
database?: string; // Or metadata.database
id?: string; // Exactly one of id and filter
idType?: "auto" | "objectId" | "string";
filter?: DocumentQuery;
multi?: boolean; // Delete all matching documents
confirm?: boolean; // Required for an empty filter
}
Output:
interface DeleteOutput {
acknowledged: boolean;
deletedCount: number;
}
mongo.documents.count#
Count documents matching a query.
const result = await client.exec(
["mongo", "documents", "count"],
{ query: { status: "active" } },
{ metadata: { collection: "users" } }
);
Input:
interface CountInput {
query?: DocumentQuery;
}
Output:
interface CountOutput {
count: number;
}
mongo.documents.aggregate#
Execute an aggregation pipeline.
const result = await client.exec(
["mongo", "documents", "aggregate"],
{
pipeline: [
{ $match: { status: "active" } },
{ $group: { _id: "$country", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
],
options: {
allowDiskUse: true,
maxTimeMS: 5000
}
},
{ metadata: { collection: "users" } }
);
Input:
interface AggregateInput {
pipeline: AggregationStage[];
options?: AggregationOptions;
}
type AggregationStage = Document;
interface AggregationOptions {
allowDiskUse?: boolean;
maxTimeMS?: number;
}
Output:
interface AggregateOutput {
results: MongoDocument[];
}
Index Procedures#
mongo.indexes.list#
List all indexes on a collection.
const result = await client.exec(
["mongo", "indexes", "list"],
{},
{ metadata: { collection: "users" } }
);
Input:
interface ListIndexesInput {
// Collection from metadata
}
Output:
interface ListIndexesOutput {
indexes: IndexInfo[];
}
interface IndexInfo {
name: string;
key: IndexSpec;
unique?: boolean;
sparse?: boolean;
}
type IndexSpec = Record<string, 1 | -1 | "text" | "2dsphere">;
mongo.indexes.create#
Create an index on a collection.
// Simple index
const result = await client.exec(
["mongo", "indexes", "create"],
{
keys: { email: 1 },
options: { unique: true }
},
{ metadata: { collection: "users" } }
);
// Compound index
const result = await client.exec(
["mongo", "indexes", "create"],
{
keys: { status: 1, createdAt: -1 },
options: { name: "status_created_idx" }
},
{ metadata: { collection: "users" } }
);
// Text index
const result = await client.exec(
["mongo", "indexes", "create"],
{
keys: { title: "text", description: "text" }
},
{ metadata: { collection: "articles" } }
);
Input:
interface CreateIndexInput {
keys: IndexSpec;
options?: IndexOptions;
}
interface IndexOptions {
name?: string;
unique?: boolean;
sparse?: boolean;
background?: boolean;
expireAfterSeconds?: number; // TTL index
}
Output:
interface CreateIndexOutput {
name: string; // Index name
}
mongo.indexes.drop#
Drop an index from a collection.
const result = await client.exec(
["mongo", "indexes", "drop"],
{ name: "email_1" },
{ metadata: { collection: "users" } }
);
Input:
interface DropIndexInput {
name: string; // Index name to drop
}
Output:
interface DropIndexOutput {
dropped: boolean;
}
Type Definitions#
Core Types#
// Documents
type DocumentId = string | ObjectId;
type BaseDocument = Document;
type MongoDocument = BaseDocument;
type DocumentQuery = Filter<Document>;
type DocumentUpdate = UpdateFilter<Document>;
// Metadata
interface BaseMeta extends Record<string, unknown> {
database?: string; // Override database name
}
interface CollectionMeta extends BaseMeta {
collection: string; // Collection name (required)
}
// Pagination
interface PaginationInput {
page?: number; // Page number (1-indexed)
limit?: number; // Items per page
}
interface PaginationOutput {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
}
// Sorting
type SortOrder = 1 | -1;
type SortSpec = Record<string, SortOrder>;
// Indexes
type IndexSpec = Record<string, 1 | -1 | "text" | "2dsphere">;
interface IndexOptions {
name?: string;
unique?: boolean;
sparse?: boolean;
background?: boolean;
expireAfterSeconds?: number;
}
interface IndexInfo {
name: string;
key: IndexSpec;
unique?: boolean;
sparse?: boolean;
}
// Collections
interface CollectionOptions {
capped?: boolean;
size?: number;
max?: number;
validator?: Document;
}
interface CollectionStats {
count: number;
size: number;
avgObjSize: number;
storageSize: number;
nindexes: number;
totalIndexSize: number;
}
// Database
interface DatabaseInfo {
name: string;
collections: number;
views: number;
sizeOnDisk: number;
empty: boolean;
}
// Aggregation
type AggregationStage = Document;
interface AggregationOptions {
allowDiskUse?: boolean;
maxTimeMS?: number;
}
// Bulk Operations
interface BulkInsertOne {
insertOne: { document: Document };
}
interface BulkUpdateOne {
updateOne: {
filter: DocumentQuery;
update: DocumentUpdate;
upsert?: boolean;
};
}
interface BulkDeleteOne {
deleteOne: { filter: DocumentQuery };
}
type BulkOperation = BulkInsertOne | BulkUpdateOne | BulkDeleteOne;
interface BulkWriteResult {
acknowledged: boolean;
insertedCount: number;
matchedCount: number;
modifiedCount: number;
deletedCount: number;
upsertedCount: number;
}
Typed Procedures Interface#
For compile-time autocomplete, use the MongoProcedures type:
import type { MongoProcedures } from "@procedures/mongo";
import { createTypedCaller } from "@procedures/client";
const call = createTypedCaller<MongoProcedures>(client.call);
// Full autocomplete on path and input!
const result = await call(
["mongo", "documents", "find"],
{ query: { status: "active" } },
{ metadata: { collection: "users" } }
);
// result is typed as FindOutput
Type Guards and Utilities#
// Check if metadata has collection
function hasCollection(metadata: Record<string, unknown>): metadata is CollectionMeta;
// Get collection metadata, throws if missing
function requireCollection(metadata: Record<string, unknown>): CollectionMeta;
// Get base metadata (database override)
function getBaseMeta(metadata: Record<string, unknown>): BaseMeta;
Usage Examples#
Basic CRUD Operations#
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import { connect } from "@procedures/mongo";
// Connect to MongoDB
await connect({
uri: "mongodb://localhost:27017",
database: "myapp"
});
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
// Create a user
const insertResult = await client.exec(
["mongo", "documents", "insert"],
{
documents: {
name: "Alice Johnson",
email: "alice@example.com",
age: 28,
status: "active",
createdAt: new Date()
}
},
{ metadata: { collection: "users" } }
);
// Find active users
const findResult = await client.exec(
["mongo", "documents", "find"],
{
query: { status: "active" },
sort: { createdAt: -1 },
limit: 10
},
{ metadata: { collection: "users" } }
);
console.log(findResult.documents);
console.log(findResult.pagination);
// Update a user
await client.exec(
["mongo", "documents", "update"],
{
filter: { email: "alice@example.com" },
update: { $set: { status: "inactive" } }
},
{ metadata: { collection: "users" } }
);
// Delete inactive users
await client.exec(
["mongo", "documents", "delete"],
{
filter: { status: "inactive" },
multi: true
},
{ metadata: { collection: "users" } }
);
Aggregation Pipeline#
// Group users by country and count
const result = await client.exec(
["mongo", "documents", "aggregate"],
{
pipeline: [
{ $match: { status: "active" } },
{ $group: {
_id: "$country",
count: { $sum: 1 },
avgAge: { $avg: "$age" }
}
},
{ $sort: { count: -1 } },
{ $limit: 10 }
]
},
{ metadata: { collection: "users" } }
);
console.log(result.results);
Index Management#
// Create unique email index
await client.exec(
["mongo", "indexes", "create"],
{
keys: { email: 1 },
options: { unique: true, name: "email_unique_idx" }
},
{ metadata: { collection: "users" } }
);
// Create compound index
await client.exec(
["mongo", "indexes", "create"],
{
keys: { status: 1, createdAt: -1 },
options: { name: "status_created_idx" }
},
{ metadata: { collection: "users" } }
);
// List all indexes
const indexes = await client.exec(
["mongo", "indexes", "list"],
{},
{ metadata: { collection: "users" } }
);
console.log(indexes.indexes);
// Drop an index
await client.exec(
["mongo", "indexes", "drop"],
{ name: "status_created_idx" },
{ metadata: { collection: "users" } }
);
Collection Management#
// Create capped collection for logs
await client.exec(
["mongo", "collections", "create"],
{
name: "logs",
options: {
capped: true,
size: 10485760, // 10MB
max: 1000
}
}
);
// List all collections
const collections = await client.exec(
["mongo", "collections", "list"],
{ filter: "user" } // Filter collections containing "user"
);
console.log(collections.collections);
// Get collection stats
const stats = await client.exec(
["mongo", "collections", "stats"],
{},
{ metadata: { collection: "users" } }
);
console.log(stats);
Direct Usage (Without RPC)#
import {
connect,
findProcedure,
insertProcedure,
requireCollection
} from "@procedures/mongo";
await connect({ database: "myapp" });
// Use procedures directly
const findResult = await findProcedure.handler(
{ query: { status: "active" }, limit: 10 },
{ metadata: { collection: "users" } }
);
const insertResult = await insertProcedure.handler(
{ documents: { name: "Bob", email: "bob@example.com" } },
{ metadata: { collection: "users" } }
);
Multi-Database Support#
// Default database
await client.exec(
["mongo", "documents", "find"],
{ query: {} },
{ metadata: { collection: "users" } }
);
// Override database in metadata
await client.exec(
["mongo", "documents", "find"],
{ query: {} },
{ metadata: { database: "analytics", collection: "events" } }
);
Integration with Ecosystem#
Bundle Packages#
client-mongo can be included in bundle packages:
// In bundle-data/src/register.ts
import "@procedures/mongo"; // registers its procedures
import "@procedures/sqlite"; // registers its procedures
import "@procedures/s3/register.js";
Type-Safe Calling#
import type { MongoProcedures } from "@procedures/mongo";
type AllProcedures = MongoProcedures & OtherProcedures;
const call = createTypedCaller<AllProcedures>(client.call);
Dependencies#
Runtime Dependencies#
- @procedures/client - Core client procedure system
- mongodb (^7.0.0) - Official MongoDB Node.js driver
Development Dependencies#
- @mark1russell7/cue - Configuration and type utilities
- typescript (^5.9.3) - TypeScript compiler
- @types/node (^22.0.0) - Node.js type definitions
Package Structure#
client-mongo/
├── src/
│ ├── connection.ts # Connection management
│ ├── types.ts # Core type definitions
│ ├── procedures.types.ts # Typed procedures interface
│ ├── register.ts # Auto-registration
│ ├── index.ts # Main entry point
│ ├── procedures/
│ │ ├── schema.ts # Minimal schema helpers
│ │ ├── index.ts # Procedure barrel export
│ │ ├── database.ping.ts
│ │ ├── database.info.ts
│ │ ├── collections.list.ts
│ │ ├── collections.create.ts
│ │ ├── collections.drop.ts
│ │ ├── collections.stats.ts
│ │ ├── documents.find.ts
│ │ ├── documents.get.ts
│ │ ├── documents.insert.ts
│ │ ├── documents.update.ts
│ │ ├── documents.delete.ts
│ │ ├── documents.count.ts
│ │ ├── documents.aggregate.ts
│ │ ├── indexes.list.ts
│ │ ├── indexes.create.ts
│ │ └── indexes.drop.ts
├── dist/ # Compiled output
├── package.json
├── tsconfig.json
└── README.md
Development#
Build#
npm run build
Type Check#
npm run typecheck
Clean#
npm run clean
License#
MIT
Author#
Mark Russell marktheprogrammer17@gmail.com