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

@kya-os/provider-registry

v0.1.7

Published

Single source of truth for provider definitions and provider-type mapping

Readme

@kya-os/provider-registry

Single source of truth for provider definitions and provider-type mapping used across the MCP-I framework.

Overview

This package centralizes provider metadata, provider-type detection, and authentication routing logic. It eliminates hardcoded provider lists scattered across the codebase and provides a consistent, testable interface for provider management.

Features

  • Single Source of Truth: Centralized provider definitions
  • Type Safety: Full TypeScript support with Zod validation
  • Dependency Injection: Interface-based design for testability
  • Runtime Configuration: Load custom providers from JSON/config
  • Default Providers: Pre-populated with common OAuth providers
  • oauthProviderId Uniqueness: Enforced uniqueness for OAuth provider IDs
  • Seal Capability: Lock registry in production to prevent modifications
  • Consistent Fallback Behavior: Documented, predictable behavior for unknown providers

Installation

pnpm add @kya-os/provider-registry

Usage

Basic Usage (Default Registry)

import { defaultProviderRegistry } from '@kya-os/provider-registry';

// Check if a provider is OAuth
if (defaultProviderRegistry.isOAuthProvider('github')) {
  // Handle OAuth flow
}

// Map auth mode to consent provider type
const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
  'credentials',
  undefined
);
// Returns: 'credential'

Dependency Injection (Testing)

import { ProviderRegistry, IProviderRegistry, createDefaultProviderRegistry } from '@kya-os/provider-registry';

// Create isolated registry for tests
const testRegistry = createDefaultProviderRegistry();

// Or create with custom providers
const customRegistry = new ProviderRegistry([
  {
    id: 'test-provider',
    displayName: 'Test Provider',
    authType: 'oauth2',
  },
]);

// Inject into your class
class MyAgent {
  constructor(private providerRegistry: IProviderRegistry = defaultProviderRegistry) {}
  
  handleAuth(provider: string) {
    if (this.providerRegistry.isOAuthProvider(provider)) {
      // OAuth flow
    }
  }
}

Registering Custom Providers

import { defaultProviderRegistry } from '@kya-os/provider-registry';

// Register a custom credential provider (e.g., for enterprise auth)
defaultProviderRegistry.registerProvider({
  id: 'my-company-auth',
  displayName: 'My Company Login',
  authType: 'password',
  ui: {
    description: 'Sign in with My Company credentials',
  },
});

// Register a custom OAuth provider
defaultProviderRegistry.registerProvider({
  id: 'enterprise-sso',
  displayName: 'Enterprise SSO',
  authType: 'oauth2',
  oauthProviderId: 'enterprise-sso',
  defaultScopes: ['openid', 'profile', 'email'],
});

Loading from Configuration

import { defaultProviderRegistry } from '@kya-os/provider-registry';

// Load from JSON config
const config = {
  providers: [
    {
      id: 'my-provider',
      displayName: 'My Provider',
      authType: 'oauth2',
    },
  ],
};

defaultProviderRegistry.loadFromConfig(config);

Environment Variable Configuration

// Load from environment variable (JSON string)
const envConfig = process.env.MCP_PROVIDER_REGISTRY;
if (envConfig) {
  const config = JSON.parse(envConfig);
  defaultProviderRegistry.loadFromConfig(config);
}

// Seal registry to prevent further modifications in production
defaultProviderRegistry.seal();

Finding Providers by OAuth Provider ID

import { defaultProviderRegistry } from '@kya-os/provider-registry';

// Find provider by oauthProviderId (useful when IDs differ)
defaultProviderRegistry.registerProvider({
  id: 'custom-oauth',
  authType: 'oauth2',
  oauthProviderId: 'my-custom-oauth-id',
});

const provider = defaultProviderRegistry.findByOauthProviderId('my-custom-oauth-id');
// Returns: { id: 'custom-oauth', ... }

API Reference

IProviderRegistry Interface

interface IProviderRegistry {
  getProvider(id: string): ProviderDefinition | undefined;
  findByOauthProviderId(oauthProviderId: string): ProviderDefinition | undefined;
  isKnownProvider(id: string): boolean;
  isOAuthProvider(id: string): boolean;
  isCredentialProvider(id: string): boolean;
  mapAuthModeToConsentProvider(authMode?: AuthMode | string, oauthProviderId?: string): ConsentProviderType;
  mapProviderToConsentProvider(providerId: string): ConsentProviderType;
  determineProviderTypeFromAuthMode(authMode?: string, oauthIdentityProvider?: string): ConsentProviderType; // @deprecated
  registerProvider(def: ProviderDefinition, opts?: { overwrite?: boolean }): void;
  listProviders(): ProviderDefinition[];
  loadFromConfig(config: unknown): void;
  seal(): void;
  isSealed(): boolean;
}

ProviderDefinition Type

interface ProviderDefinition {
  id: string;                    // Unique identifier (e.g., 'github', 'my-company-auth')
  displayName?: string;          // Human-friendly name
  authType: ProviderAuthType;    // 'oauth2', 'password', 'verifiable_credential', etc.
  oauthProviderId?: string;      // OAuth provider ID (must be unique across registry)
  defaultScopes?: string[];      // Default OAuth scopes
  oauthConfig?: OAuthProviderConfig;     // OAuth endpoint configuration
  credentialConfig?: CredentialProviderConfig; // Credential flow configuration
  ui?: {
    icon?: string;
    description?: string;
  };
  metadata?: Record<string, unknown>;    // Custom metadata (secret names, not secrets!)
}

OAuthProviderConfig Type

Configuration for OAuth provider flows:

interface OAuthProviderConfig {
  authorizationEndpoint: string;   // OAuth authorization URL
  tokenEndpoint: string;           // OAuth token URL
  userInfoEndpoint?: string;       // User info endpoint
  defaultScopes?: string[];        // Default scopes to request
  supportsPKCE?: boolean;          // Whether PKCE is supported
  requiresClientSecret?: boolean;  // Whether client secret is required
  tokenEndpointAuthMethod?: 'client_secret_post' | 'client_secret_basic';
  responseType?: string;           // OAuth response type (default: 'code')
  grantType?: string;              // OAuth grant type (default: 'authorization_code')
  customParams?: Record<string, string>; // Custom OAuth params (audience, etc.)
  authUrlTemplate?: string;        // URL template with {placeholders}
}

CredentialProviderConfig Type

Configuration for credential/password authentication:

interface CredentialProviderConfig {
  authEndpoint: string;            // Endpoint to POST credentials
  httpMethod?: 'POST' | 'PUT';     // HTTP method
  contentType?: 'application/json' | 'application/x-www-form-urlencoded';
  requestBodyTemplate?: {
    identityField?: string;        // Field name for email/username
    passwordField?: string;        // Field name for password
    additionalFields?: Record<string, string>;
  };
  responseFields?: {
    sessionTokenPath?: string;     // JSON path to session token
    userIdPath?: string;           // JSON path to user ID
    userEmailPath?: string;        // JSON path to user email
    userDisplayNamePath?: string;  // JSON path to display name
    expiresInPath?: string;        // JSON path to token expiry
  };
  successCheck?: {
    path?: string;                 // JSON path to check for success
    expectedValue?: string | boolean;
  };
  useCookieSession?: boolean;      // Whether to use cookies for session
  cookieNames?: string;            // Cookie names to extract
  customHeaders?: Record<string, string>;
  requiresCsrf?: boolean;          // Whether CSRF token is required
}

Mapping Semantics

mapAuthModeToConsentProvider(authMode?, oauthProviderId?)

Deterministic precedence rules:

  1. Explicit authMode (non-oauth) → Direct mapping

    • 'credentials', 'password''credential'
    • 'magic-link''magic_link'
    • 'otp''otp'
    • 'verifiable_credential', 'idv', 'mdl''verifiable_credential'
    • 'consent-only', 'none', '''none'
  2. If authMode === 'oauth' or oauthProviderId present → Consult registry

    • Known provider → Map based on provider's authType
    • Unknown provider → 'oauth2' (with warning log)
  3. Fallback'none'

mapProviderToConsentProvider(providerId)

  • Known provider → Map based on provider's authType
  • Unknown provider → 'none' (with warning log)

Fallback Behavior Rationale

The different fallbacks are intentional:

  • mapAuthModeToConsentProvider with unknown oauthProviderId → 'oauth2': The presence of an oauthProviderId strongly implies an OAuth flow, even if the provider isn't registered.

  • mapProviderToConsentProvider with unknown providerId → 'none': Direct provider lookup should fail safely without assumptions.

Both log warnings to help identify configuration issues.

Default Providers

The registry comes pre-populated with common providers:

OAuth Providers: GitHub, Google, Microsoft, Discord, Slack, Apple, LinkedIn, Twitter, Facebook, Okta, Auth0

Credential Provider: credentials (generic placeholder)

Note: Customer-specific providers (like HardwareWorld) should NOT be in default providers. Register them via:

  • registerProvider() at runtime
  • loadFromConfig() with custom configuration
  • MCP_PROVIDER_REGISTRY environment variable

Security: Secrets Handling

⚠️ NEVER store actual secrets in provider definitions. Store only secret references (names).

Provider definitions may contain endpoint URLs and configuration, but client secrets, API keys, and other sensitive credentials must NOT be stored in the registry.

The Correct Approach

Store secret names in metadata and read actual secrets from secure storage at runtime:

// ✅ CORRECT: Store secret name, not secret value
defaultProviderRegistry.registerProvider({
  id: 'enterprise-oauth',
  displayName: 'Enterprise SSO',
  authType: 'oauth2',
  oauthConfig: {
    authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
    tokenEndpoint: 'https://sso.example.com/oauth/token',
  },
  metadata: {
    clientSecretName: 'ENTERPRISE_SSO_CLIENT_SECRET', // Name, not value!
    clientId: 'my-app-client-id', // Client ID is often public, OK to store
  },
});

// At runtime, read secret from secure storage
async function getProviderSecret(providerId: string): Promise<string> {
  const provider = defaultProviderRegistry.getProvider(providerId);
  const secretName = provider?.metadata?.clientSecretName as string;
  
  // Read from Cloudflare Secrets, AWS Secrets Manager, vault, etc.
  return env[secretName] || await secretsManager.getSecret(secretName);
}

What Can Be Stored in Registry

| Field | OK to Store | Notes | |-------|-------------|-------| | authorizationEndpoint | ✅ Yes | Public URL | | tokenEndpoint | ✅ Yes | Public URL | | clientId | ✅ Usually | Often public (check your provider) | | clientSecret | ❌ NO | Store as metadata.clientSecretName | | apiKey | ❌ NO | Store as metadata.apiKeyName | | defaultScopes | ✅ Yes | Configuration, not secret |

Credential Provider Security

For password/credential flows, the credentialConfig describes how to authenticate, not with what credentials:

// ✅ CORRECT: Describe flow structure, not credentials
defaultProviderRegistry.registerProvider({
  id: 'legacy-api',
  authType: 'password',
  credentialConfig: {
    authEndpoint: 'https://api.example.com/auth/login',
    contentType: 'application/json',
    requestBodyTemplate: {
      identityField: 'email',     // Field name, not value
      passwordField: 'password',  // Field name, not value
    },
    responseFields: {
      sessionTokenPath: 'data.token',
    },
  },
  metadata: {
    // If the API requires static headers like API keys:
    apiKeyHeaderName: 'X-API-Key',
    apiKeySecretName: 'LEGACY_API_KEY', // Read from env/secrets at runtime
  },
});

Production Best Practices

1. Load Custom Providers Early

import { defaultProviderRegistry } from '@kya-os/provider-registry';

// In your server initialization
async function initializeRegistry() {
  // Load from config/environment
  if (process.env.MCP_PROVIDER_REGISTRY) {
    defaultProviderRegistry.loadFromConfig(
      JSON.parse(process.env.MCP_PROVIDER_REGISTRY)
    );
  }
  
  // Seal to prevent runtime modifications
  defaultProviderRegistry.seal();
}

2. Use Dependency Injection for Testing

import { createDefaultProviderRegistry, IProviderRegistry } from '@kya-os/provider-registry';

class MyService {
  constructor(private registry: IProviderRegistry = defaultProviderRegistry) {}
}

// In tests
const testRegistry = createDefaultProviderRegistry();
testRegistry.registerProvider({ id: 'mock', authType: 'oauth2' });
const service = new MyService(testRegistry);

3. Custom Logger for Metrics

import { createDefaultProviderRegistry, RegistryLogger } from '@kya-os/provider-registry';

const metricsLogger: RegistryLogger = {
  warn(message, context) {
    myMetricsClient.increment('provider_registry.warning', {
      message,
      ...context,
    });
    console.warn(message, context);
  },
};

const registry = createDefaultProviderRegistry(metricsLogger);

4. Registry Lifecycle & Multi-tenant Deployments

Lifecycle expectations for the singleton:

  1. Initialization: The defaultProviderRegistry is lazily created on first access (no module-load side effects).

  2. Configuration: Load project-specific providers early in your application startup:

    // At server/worker startup
    if (env.MCP_PROVIDER_REGISTRY) {
      defaultProviderRegistry.loadFromConfig(JSON.parse(env.MCP_PROVIDER_REGISTRY));
    }
  3. Sealing: Call seal() after configuration to prevent accidental runtime modifications:

    defaultProviderRegistry.seal();
    // After this, registerProvider() and loadFromConfig() will throw
  4. Runtime: Use the sealed registry for lookups throughout the application lifecycle.

Multi-tenant deployments:

For multi-tenant scenarios where different projects need different providers, prefer per-project registry instances instead of mutating the global singleton:

import { ProviderRegistry, defaultProviders } from '@kya-os/provider-registry';

function createProjectRegistry(projectConfig: ProviderConfig): ProviderRegistry {
  // Start with default providers
  const registry = new ProviderRegistry(defaultProviders);
  
  // Add project-specific providers
  registry.loadFromConfig(projectConfig);
  
  // Seal and return
  registry.seal();
  return registry;
}

// Each tenant gets their own isolated registry
const tenant1Registry = createProjectRegistry(tenant1Config);
const tenant2Registry = createProjectRegistry(tenant2Config);

Testing:

Use resetDefaultProviderRegistryForTests() in test setup to ensure isolation:

import { resetDefaultProviderRegistryForTests } from '@kya-os/provider-registry';

beforeEach(() => {
  resetDefaultProviderRegistryForTests();
});

Integration Examples

Cloudflare Agent

import { defaultProviderRegistry, IProviderRegistry } from '@kya-os/provider-registry';

export class MCPICloudflareAgent {
  constructor(
    private providerRegistry: IProviderRegistry = defaultProviderRegistry
  ) {}

  async handleOAuthRequired(error: OAuthRequiredError) {
    const provider = error.provider?.toLowerCase() || '';
    
    if (this.providerRegistry.isCredentialProvider(provider)) {
      // Build credential consent URL
    } else if (this.providerRegistry.isOAuthProvider(provider)) {
      // Build OAuth URL
    }
  }
}

Consent UI

import { defaultProviderRegistry } from '@kya-os/provider-registry';

const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
  authMode,
  oauthIdentity?.provider
);

const provider = defaultProviderRegistry.getProvider(providerId);
if (provider?.ui?.icon) {
  // Render provider icon
}

Testing

import { ProviderRegistry, createDefaultProviderRegistry } from '@kya-os/provider-registry';

describe('MyComponent', () => {
  it('should handle OAuth providers', () => {
    const registry = new ProviderRegistry([
      { id: 'github', displayName: 'GitHub', authType: 'oauth2' },
    ]);
    
    expect(registry.isOAuthProvider('github')).toBe(true);
  });

  it('should use isolated registry', () => {
    const registry = createDefaultProviderRegistry();
    registry.registerProvider({ id: 'custom', authType: 'password' });
    
    // Won't affect other tests or global singleton
    expect(registry.isCredentialProvider('custom')).toBe(true);
  });
});

Migration Guide

Replacing Hardcoded Lists

Before:

const KNOWN_OAUTH_PROVIDERS = ['github', 'google', 'microsoft'];
const isOAuth = KNOWN_OAUTH_PROVIDERS.includes(provider);

After:

import { defaultProviderRegistry } from '@kya-os/provider-registry';
const isOAuth = defaultProviderRegistry.isOAuthProvider(provider);

Replacing Provider Type Detection

Before:

function getProviderType(authMode?: string, oauthProvider?: string) {
  if (authMode === 'credentials') return 'credential';
  if (oauthProvider) return 'oauth2';
  return 'none';
}

After:

import { defaultProviderRegistry } from '@kya-os/provider-registry';
const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
  authMode,
  oauthProvider
);

Using findByOauthProviderId

Before:

// Manual lookup with potential mismatch
const provider = registry.getProvider(oauthIdentityProvider);

After:

// Proper lookup using oauthProviderId index
const provider = registry.findByOauthProviderId(oauthIdentityProvider);

License

MIT