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

@oomol-lab/connector-mastra

v0.1.0

Published

Mastra ToolProvider for the OOMOL Connector: surface your connected accounts (Gmail, Slack, GitHub, Notion, ...) as tools in the Mastra editor and in code-configured agents.

Readme

@oomol-lab/connector-mastra

npm CI types license

Your OOMOL connections as Mastra tools. A Mastra ToolProvider over the OOMOL Connector: every action the gateway models (gmail.search_threads, slack.post_message, github.create_issue, …) shows up in the Mastra editor's tool picker and can be handed to a code-configured agent. Connecting an account runs through the editor too; tokens, refresh, and credential storage stay on the gateway, exactly as with the SDK.

import { MastraEditor } from "@mastra/editor";
import { OomolToolProvider } from "@oomol-lab/connector-mastra";

const editor = new MastraEditor({
  toolProviders: {
    oomol: new OomolToolProvider({ apiKey: process.env.OOMOL_API_KEY! }),
  },
});

Two providers ship in the package, mirroring the SDK's two personal clients:

| Provider | Talks to | Construct with | | --- | --- | --- | | OomolToolProvider | the hosted gateway (Connector) | the same fields as new Connector({...}), or { connector } | | OomolOpenToolProvider | the open-source, self-hosted runtime (OpenConnector) | the same fields as new OpenConnector({...}), or { connector } |

Install

npm install @oomol-lab/connector-mastra @mastra/core   # or: bun add / pnpm add / yarn add

@mastra/core is a peer dependency (types only, ^1.64). The package itself depends on nothing but @oomol-lab/connector; it imports no Mastra code at runtime and needs no Zod. Node ≥ 20. The editor example above also needs @mastra/editor, which this package neither requires nor imports:

npm install @mastra/editor

You need an OOMOL Connector API key (api-…): https://console.oomol.com/api-key. Accounts can be connected two ways: in the console, or through the provider's authorize flow, which is what the editor's "Connect" button drives. See Connecting an account. Removing a connection is console-only.

In the editor

Register the provider under any key; that key is what the editor stores on agents:

const editor = new MastraEditor({
  toolProviders: {
    oomol: new OomolToolProvider({
      apiKey: process.env.OOMOL_API_KEY!,
      allowedToolkits: ["gmail", "slack", "github"],       // optional narrowing
      allowedTools: { gmail: ["gmail.search_*"] },        // optional, per toolkit
    }),
  },
});

In the picker, toolkits are OOMOL services and tools are OOMOL actions. Pinning a connection onto an agent uses the connections that already exist on your account (apps.list() in the SDK), and "Connect" runs a real OAuth flow through the gateway, so a missing account can be added without leaving the editor. See Connecting an account for what that flow needs. "Disconnect" stays hidden: removing a connection is done in the OOMOL console.

In code

Skip the editor and hand tools straight to an agent:

import { Agent } from "@mastra/core/agent";
import { OomolToolProvider } from "@oomol-lab/connector-mastra";

const oomol = new OomolToolProvider({ apiKey: process.env.OOMOL_API_KEY! });

// Runs on the client's default connection, or name one of yours.
const tools = await oomol.resolveTools(["gmail.search_threads", "slack.post_message"], undefined, {
  connectionName: "work",
});

const agent = new Agent({
  id: "inbox-assistant",
  name: "Inbox assistant",
  instructions: "Triage the user's inbox.",
  model: "openai/gpt-4o-mini",
  tools,
});

resolveTools accepts the same per-tool description overrides Mastra's other providers do ({ "gmail.search_threads": { description: "…" } } as the second argument). See examples/ for a runnable version.

Self-hosted runtime

import { OomolOpenToolProvider } from "@oomol-lab/connector-mastra";

new OomolOpenToolProvider();                                            // http://localhost:3000, no token
new OomolOpenToolProvider({ baseUrl: "https://connector.internal", runtimeToken: "oct_…" });
new OomolOpenToolProvider({ runtimeToken: "oct_…", adminToken: "…" });   // adminToken is for `authorize` only
new OomolOpenToolProvider({ connector: existingOpenConnector });

Same surface as the hosted provider; getHealth() is backed by the runtime's /v1/health probe (token-aware) instead of the gateway's root /health.

authorize is admin-scoped on a self-hosted runtime, and what it needs depends on how you configured that runtime. One with no authentication at all accepts it unauthenticated. Once runtime tokens are enforced an adminToken becomes mandatory: without one the runtime answers 403 (Configure an admin token to manage connections), and with one configured a runtime token (oct_…) is refused with 401. The SDK sends adminToken on the connect routes and runtimeToken everywhere else, so pass both when your runtime enforces auth.

Options

Both providers accept, on top of the SDK config they pass through:

| Option | Default | Effect | | --- | --- | --- | | allowedToolkits | all | Keep only toolkits (services) whose slug matches: exact "gmail" or prefix wildcard "google*". Enforced on resolveTools too, not just discovery. | | allowedTools | all | Per-toolkit tool allowlist, keyed by toolkit slug, patterns over tool slugs ("gmail.search_*"). An absent toolkit is unfiltered; [] hides it entirely. | | validateOutput | false | Attach each action's outputSchema so Mastra validates tool results. Off by default: a strict schema turns a provider response that drifts from it into a tool error, even though the call succeeded. | | authorizeReturnUri | — | Where the browser lands after an authorize flow completes, with status and service appended. Omit and the backend renders its own result page, which loses nothing: Mastra learns the outcome from getAuthStatus, not from the redirect. |

The allowlist semantics are Mastra's own (BaseToolProviderOptions), so a config written for ComposioToolProvider reads the same here.

How Mastra's contract maps onto the SDK

| Mastra ToolProvider | Backed by | Notes | | --- | --- | --- | | listToolkits / listToolkitsVNext | catalog.providers() | slug = service, name = displayName, icon = iconUrl | | listTools / listToolsVNext | catalog.actions(service) | One call with a toolkit; a bounded fan-out over every allowed toolkit without. search is a case-insensitive substring match; paging is in memory. | | getToolSchema | catalog.action(slug).inputSchema | null on 404, any other failure is rethrown | | resolveTools / resolveToolsVNext | catalog.actions + execute | Tools carry the action's JSON Schema verbatim; Mastra validates input with it. execute forwards Mastra's abortSignal. | | listConnections | apps.list() | connectionId is the backend's connection id; status mapped to Mastra's four states. userId(s) are ignored: the account is personal. | | getConnectionStatus | apps.list() | One call answers every tuple | | authorize | connect.oauth(toolkit) | Returns the provider's authorization URL plus the attempt handle as authId. Management-scoped, see below. | | getAuthStatus | connect.getAttempt(authId) | initiated maps to pending, connected to completed; everything else, expired included, to failed. An unknown handle reads as failed, not an error. | | getHealth | hosted: GET /health at the gateway origin (unauthenticated liveness); self-hosted: health() | Never throws; answers reachability, not key validity | | capabilities | — | multipleConnectionsPerToolkit: true, batchConnectionStatus: true, reauthorizeReusesConnectionId: false, supportsRevoke: false |

A pinned connectionId is resolved to the SDK's connectionName lazily on the tool's first execution and cached on success, so a stale pin fails that one tool call with a clear message instead of failing agent hydration, and a connection that appears later is picked up without re-resolving.

Connecting an account

authorize starts an OAuth flow and getAuthStatus polls it; the editor's connection picker drives both for you. examples/connect.ts runs the same three steps by hand.

Two things about this flow are worth knowing before you debug it.

The id you get back is not the id you pass in. OOMOL mints the connection, and its id, only when the callback lands, and it accepts no caller-supplied id at flow start. So authorize ignores opts.connectionId and the real id arrives afterwards through listConnections, which is where Mastra reads it from. capabilities.reauthorizeReusesConnectionId is false to declare exactly that. Acting on completed immediately is safe: the backend writes the connection row and flips the attempt in one transaction, so the connection is already listed and active the moment the status says so.

A side effect of that gap: Mastra keys the label and scope it records at authorize time on the returned authId, then joins them back by the connection id listConnections reports. Ours differ, so a label typed while connecting is not kept. Pinning, execution and status are unaffected. Set the label on the connection afterwards.

Authorizing needs more permission than running tools. On the hosted gateway the key's user must be creator or admin of the effective team; a plain member is refused with a 403 before the request lands. On a self-hosted runtime it is admin-scoped, see Self-hosted runtime. Running actions needs neither.

Deliberately not implemented

listConnectionFields and revokeConnection have no counterpart in the personal Connector / OpenConnector surface: the provider's connection-field declarations live on a backend route the SDK does not map, and the SDK has no delete call, since connections are removed in the OOMOL console. Both methods are absent rather than stubbed, which is the signal the Mastra editor uses to hide the matching affordances. An absent listConnectionFields also means the picker never collects extra fields, so AuthorizeOpts.config arrives empty in practice; pass it yourself and it is forwarded as the backend's per-attempt extra.

The project-scoped ProjectConnector (connect accounts on behalf of your end-users) is out of scope for this package.

Development

bun install
bun run check   # lint + typecheck + build + tests at 100% coverage + type-level acceptance

License

MIT