npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mzhub/gqp

v0.1.1

Published

The Universal Agent Data Layer - Consolidate 50+ tools into 1 dynamic navigable tool

Readme

@mzhub/gqp - The Universal Agent Data Layer


The Problem

AI agents using tools face two critical problems:

  1. Token explosion: 50+ tools x 200 tokens each = 10,000+ tokens just for tool descriptions
  2. Tool confusion: Agents pick wrong tools, can't understand relationships between data

The Solution

GQP consolidates all data access into ONE dynamic tool that navigates a graph of your data:

WITHOUT GQP:                    WITH GQP:
+-------------------+           +-------------------+
| 50 separate tools |           | 1 universal tool  |
| 10,000 tokens     |    ->     | 200-500 tokens    |
| No relationships  |           | Full graph nav    |
+-------------------+           +-------------------+

Result: 95%+ token reduction, smarter agents, zero tool confusion.


Quick Start

Installation

npm install @mzhub/gqp

Basic Usage

import { GQP } from "@mzhub/gqp";
import { fromPrisma } from "@mzhub/gqp/prisma";
import { prisma } from "./db";

// Create graph from your database
const graph = new GQP({
  sources: {
    database: fromPrisma(prisma),
  },
});

// Agent calls this ONE tool instead of 50+ separate tools
const result = await graph.handleToolCall({
  action: "explore",
});

// Returns available data nodes
// {
//   availableNodes: [
//     { name: 'User', description: 'User accounts', operations: ['query', 'filter'] },
//     { name: 'Order', description: 'Customer orders', operations: ['query', 'filter'] },
//     { name: 'Product', description: 'Product catalog', operations: ['query', 'filter'] }
//   ],
//   message: 'Start by navigating to one of these nodes',
//   totalNodes: 3
// }

How It Works

The One Tool Pattern

Instead of giving agents 50+ separate tools, GQP provides ONE tool with three actions:

| Action | Description | When to Use | | ---------- | ------------------------- | ---------------------------- | | explore | List available data nodes | Start here to discover data | | navigate | Focus on a specific node | See fields and relationships | | query | Search and filter data | Get actual results |

Agent Workflow

// Step 1: Explore - What data is available?
await graph.handleToolCall({ action: "explore" });
// -> ["User", "Order", "Product"]

// Step 2: Navigate - What's in Order?
await graph.handleToolCall({ action: "navigate", node: "Order" });
// -> { fields: [...], relatedNodes: ["User", "Product"], capabilities: [...] }

// Step 3: Query - Get the data
await graph.handleToolCall({
  action: "query",
  node: "Order",
  filters: { status: "pending" },
});
// -> { data: [...], meta: { total: 42 } }

Why Relations Matter

Relations are the key to making your agent smarter, not just smaller. When you define relations between nodes, GQP uses them in two ways that directly improve agent behavior:

1. Discovery - When the agent explores or navigates your graph, relations appear as connected nodes. This gives the agent a map of your data model, so it understands that Products have Vendors, Orders have Customers, etc.

// Agent navigates to Product and sees:
// {
//   operations: ['search', 'getDetails'],
//   relations: [{ name: 'vendor', target: 'Vendor' }]
// }
// Now the agent KNOWS it can jump to Vendor from Product

2. Guided next steps - After the agent executes a tool, the response automatically includes suggestions based on relations. This helps the agent follow logical workflows without you having to prompt-engineer the path.

// Agent searches for products, response includes:
// {
//   data: [...results...],
//   suggestions: ['Navigate to Vendor (vendor)', 'Navigate to Category (category)']
// }
// The agent now has a natural next step instead of guessing

Without relations, the agent can still explore and execute tools, but it has no sense of how your data connects. With well-mapped relations, the agent can chain operations together intelligently, like finding a product, then checking the vendor, then looking at the vendor's other products.


Best Practices

Designing Your Graph

Group related operations under one node. Each node should represent a logical entity (Product, Order, User), not individual operations. This gives the agent a clear mental model of your data.

// Good: Operations grouped by entity
Product: {
  tools: { search: ..., getDetails: ..., getReviews: ... }
}

// Avoid: One node per operation
ProductSearch: { tools: { run: ... } }
ProductDetails: { tools: { run: ... } }

Map all meaningful relationships. If two entities are connected in your data, define the relation. The more connections, the better the agent navigates.

Product: {
  relations: {
    vendor: 'Vendor',      // Product belongs to a vendor
    category: 'Category',  // Product is in a category
    orders: 'Order',       // Product appears in orders
  }
}

Write clear node descriptions. The agent sees these when exploring. A good description helps it pick the right node on the first try.

// Good: Tells the agent what it can find here
Product: {
  description: "Search product catalog by name, category, or price range";
}

// Avoid: Too vague to be useful
Product: {
  description: "Products";
}

Keep action tools separate. GQP is ideal for read/query tools. Keep write operations (create order, update cart, send message) as standalone tools so the agent treats them as deliberate actions.

const agent = createReactAgent({
  tools: [
    gqpTool, // All read/query tools wrapped
    createOrderTool, // Standalone: explicit action
    addToCartTool, // Standalone: explicit action
  ],
});

Framework Integrations

MCP (Model Context Protocol)

import { GQP } from "@mzhub/gqp";
import { serveMCP } from "@mzhub/gqp/mcp";
import { fromPrisma } from "@mzhub/gqp/prisma";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
});

// Start MCP server with single tool
serveMCP(graph, {
  name: "my-data-server",
});

OpenAI Functions

import { toOpenAIFunctions, createExecutor } from '@mzhub/gqp/openai';

const functions = toOpenAIFunctions(graph);
const executor = createExecutor(graph);

const response = await openai.chat.completions.create({
  model: 'gpt-4-turbo',
  messages: [...],
  functions,
  function_call: 'auto'
});

// Execute the function call
if (response.choices[0].message.function_call) {
  const result = await executor(response.choices[0].message.function_call);
}

LangChain

import { createTool } from "@mzhub/gqp/langchain";
import { ChatOpenAI } from "langchain/chat_models/openai";

const tool = await createTool(graph);

const agent = createToolCallingAgent({
  llm: new ChatOpenAI({ modelName: "gpt-4" }),
  tools: [tool], // Just ONE tool!
});

LangChain - Tool Wrapper Mode

Already have hand-written tools? Use wrapTools to consolidate them:

import { wrapTools } from "@mzhub/gqp/langchain";
import {
  searchProductsTool,
  getProductDetailsTool,
  searchVendorsTool,
  listOrdersTool,
} from "./tools";

// Wrap existing tools into ONE navigable interface
const gqpTool = await wrapTools({
  graph: {
    Product: {
      description: "Product catalog",
      tools: {
        search: searchProductsTool,
        getDetails: getProductDetailsTool,
      },
      relations: { vendor: "Vendor" },
    },
    Vendor: {
      description: "Vendor search",
      tools: { search: searchVendorsTool },
    },
    Order: {
      description: "Order history",
      tools: { list: listOrdersTool },
    },
  },
});

// Now: 1 tool instead of 24!
const agent = createReactAgent({
  llm: new ChatOpenAI(),
  tools: [
    gqpTool, // Wraps all read tools
    createOrderTool, // Keep action tools separate
    addToCartTool,
  ],
});

Agent workflow with wrapped tools:

// 1. Explore available nodes
{ action: 'explore' }
// -> { availableNodes: ['Product', 'Vendor', 'Order'] }

// 2. Navigate to see operations
{ action: 'navigate', node: 'Product' }
// -> { operations: ['search', 'getDetails'], relations: ['vendor'] }

// 3. Execute the actual tool
{ action: 'execute', node: 'Product', operation: 'search', params: { query: 'rice' } }
// -> Calls searchProductsTool.invoke({ query: 'rice' })

Any Framework (Zero Dependencies)

Not using LangChain? Use createToolWrapper to get the raw wrapper with zero framework dependencies. It works with plain functions, custom tool objects, or tools from any SDK:

import { createToolWrapper } from "@mzhub/gqp/langchain";

const wrapper = createToolWrapper({
  graph: {
    Product: {
      description: "Product catalog",
      tools: {
        // Plain async functions work
        search: async (params) => {
          const results = await db.products.find(params);
          return results;
        },
        // Objects with .invoke() work (LangChain, etc)
        getDetails: myLangChainTool,
        // Objects with .func() work
        getReviews: { func: async (params) => fetchReviews(params) },
      },
      relations: { vendor: "Vendor", category: "Category" },
    },
    Vendor: {
      description: "Vendor directory",
      tools: {
        search: async (params) => db.vendors.find(params),
      },
    },
  },
});

// Use with any framework - Gemini, OpenAI, custom agents, etc.
const result = await wrapper.handleToolCall({
  action: "explore",
});

const data = await wrapper.handleToolCall({
  action: "execute",
  node: "Product",
  operation: "search",
  params: { query: "rice" },
});

The wrapper accepts any tool that satisfies one of these patterns: | Tool Format | Example | Works With | | --- | --- | --- | | Plain async function | async (params) => { ... } | Any framework | | Object with .invoke() | LangChain tools, custom classes | LangChain, custom | | Object with .func() | { func: async (p) => ... } | Custom tools |

Vercel AI SDK

import { createTools } from "@mzhub/gqp/vercel-ai";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

const tools = await createTools(graph);

const result = await streamText({
  model: openai("gpt-4-turbo"),
  prompt: "Find customers with high-value orders",
  tools,
});

Data Source Adapters

Prisma (Primary)

import { fromPrisma } from "@mzhub/gqp/prisma";

const graph = new GQP({
  sources: {
    database: fromPrisma(prisma, {
      include: ["User", "Order", "Product"],
      exclude: ["_migrations"],
      descriptions: {
        User: "Customer accounts",
        Order: "Purchase orders",
      },
    }),
  },
});

REST APIs

import { fromREST } from "@mzhub/gqp/rest";

const graph = new GQP({
  sources: {
    stripe: fromREST({
      baseURL: "https://api.stripe.com/v1",
      auth: { type: "bearer", token: process.env.STRIPE_KEY },
      resources: {
        charges: {
          endpoint: "/charges",
          idField: "id",
          schema: {
            id: "String",
            amount: "Int",
            status: "String",
          },
        },
      },
    }),
  },
});

Plugins

Vector Search (Semantic Search)

import { VectorPlugin } from "@mzhub/gqp/vector";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
  plugins: [
    new VectorPlugin({
      provider: "pinecone",
      config: {
        pinecone: {
          apiKey: process.env.PINECONE_KEY,
          index: "products",
        },
        openai: {
          apiKey: process.env.OPENAI_KEY,
        },
      },
    }),
  ],
});

// Now queries support semantic search!
await graph.handleToolCall({
  action: "query",
  node: "Product",
  query: "comfortable running shoes", // Semantic search
});

Temporal Reasoning (Natural Language Dates)

import { TemporalPlugin } from "@mzhub/gqp/temporal";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
  plugins: [
    new TemporalPlugin({
      timezone: "America/New_York",
      patterns: {
        Q1: { start: "01-01", end: "03-31" },
        Q2: { start: "04-01", end: "06-30" },
      },
    }),
  ],
});

// Natural language dates work automatically
await graph.handleToolCall({
  action: "query",
  node: "Order",
  filters: { createdAt: "last week" }, // Parsed to date range
});

Caching

import { CachePlugin } from "@mzhub/gqp/cache";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
  plugins: [
    new CachePlugin({
      provider: "memory",
      ttl: 300, // 5 minutes
      maxSize: 1000,
    }),
  ],
});

API Reference

GQP Class

class GQP {
  constructor(config: GQPConfig);

  // Main tool interface
  handleToolCall(params: ToolCallParams): Promise<ToolCallResult>;

  // Get tool definition for frameworks
  getToolDefinition(): {
    name: string;
    description: string;
    inputSchema: object;
  };

  // Create navigation cursor
  createCursor(startNode?: string): GQPGraphCursor;
}

ToolCallParams

interface ToolCallParams {
  action: "explore" | "navigate" | "query" | "introspect";
  node?: string; // Required for navigate/query
  query?: string; // Semantic search query
  filters?: object; // Field filters
  options?: {
    limit?: number;
    offset?: number;
    include?: string[];
  };
}

Configuration

interface GQPConfig {
  sources: Record<string, GQPAdapter>;
  features?: {
    fuzzySearch?: boolean;
    temporalReasoning?: boolean;
    neighborhoodSize?: number; // Default: 5
    maxDepth?: number; // Default: 4
  };
  plugins?: GQPPlugin[];
}

Security

GQP includes production-ready security features that are opt-in and configurable.

Input Validation

import { GQP } from "@mzhub/gqp";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
  validation: {
    strictNodeNames: true,
    maxFilterDepth: 5,
    maxArrayInFilter: 100,
    allowedFilterOperators: ["equals", "in", "lt", "gt", "contains"],
  },
  limits: {
    maxLimit: 100,
    defaultLimit: 10,
    maxOffset: 10000,
    maxIncludeDepth: 3,
    timeout: 30000,
  },
});

Error Handling

Errors are environment-aware - detailed in development, safe in production:

import { GQPError, isGQPError } from "@mzhub/gqp";

try {
  await graph.handleToolCall({ action: "query", node: "InvalidNode" });
} catch (error) {
  if (isGQPError(error)) {
    console.log(error.code); // 'NODE_NOT_FOUND'
    console.log(error.message); // Safe for clients
  }
}

Access Control & Row-Level Security

import { createSecurityManager } from "@mzhub/gqp";

const graph = new GQP({
  sources: { db: fromPrisma(prisma) },
  security: {
    hiddenNodes: ["AuditLog", "InternalConfig"],
    hiddenFields: { User: ["passwordHash", "ssn"] },
    nodeAccess: {
      Order: (ctx) => ctx.user?.role === "admin" || ctx.user?.id != null,
    },
    rowLevelSecurity: {
      Order: (ctx) => ({ userId: ctx.user?.id }),
    },
    introspection: "nodes-only", // 'full' | 'nodes-only' | 'none'
  },
});

// Pass security context with each call
await graph.handleToolCall(
  { action: "query", node: "Order" },
  { user: { id: 123, role: "customer" } },
);

Adapter Security

Prisma Adapter: Operator allowlist, field validation, include depth limiting.

REST Adapter: SSRF protection, protocol validation, header injection prevention.

Safe JSON Utilities

import { safeJsonParse, safeJsonStringify } from "@mzhub/gqp";

const data = safeJsonParse(untrustedInput, { default: "value" });
const json = safeJsonStringify(objectWithCircularRefs);

Package Exports

// Core
import { GQP, GQPSchema, GQPGraphCursor } from "@mzhub/gqp";

// Adapters
import { fromPrisma } from "@mzhub/gqp/prisma";
import { fromREST } from "@mzhub/gqp/rest";

// Bridges
import { serveMCP } from "@mzhub/gqp/mcp";
import { toOpenAIFunctions } from "@mzhub/gqp/openai";
import { createTool } from "@mzhub/gqp/langchain";
import { createTools } from "@mzhub/gqp/vercel-ai";

// Plugins
import { VectorPlugin } from "@mzhub/gqp/vector";
import { TemporalPlugin } from "@mzhub/gqp/temporal";
import { CachePlugin } from "@mzhub/gqp/cache";

Token Efficiency Comparison

| Approach | Tools | Tokens | Setup | | ---------------- | ----- | ------------ | --------- | | Manual MCP tools | 50+ | ~10,000 | Days | | LangChain tools | 50+ | ~10,000 | Hours | | GQP | 1 | ~200-500 | 5 min |


License

MIT