@kya-os/provider-registry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +592 -0
- package/dist/default-providers.d.ts +30 -0
- package/dist/default-providers.d.ts.map +1 -0
- package/dist/default-providers.js +162 -0
- package/dist/default-providers.js.map +1 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +291 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +339 -0
- package/dist/registry.js.map +1 -0
- package/dist/schemas.d.ts +780 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +104 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# @kya-os/provider-registry
|
|
2
|
+
|
|
3
|
+
Single source of truth for provider definitions and provider-type mapping used across the MCP-I framework.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Single Source of Truth**: Centralized provider definitions
|
|
12
|
+
- **Type Safety**: Full TypeScript support with Zod validation
|
|
13
|
+
- **Dependency Injection**: Interface-based design for testability
|
|
14
|
+
- **Runtime Configuration**: Load custom providers from JSON/config
|
|
15
|
+
- **Default Providers**: Pre-populated with common OAuth providers
|
|
16
|
+
- **oauthProviderId Uniqueness**: Enforced uniqueness for OAuth provider IDs
|
|
17
|
+
- **Seal Capability**: Lock registry in production to prevent modifications
|
|
18
|
+
- **Consistent Fallback Behavior**: Documented, predictable behavior for unknown providers
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm add @kya-os/provider-registry
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Basic Usage (Default Registry)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
32
|
+
|
|
33
|
+
// Check if a provider is OAuth
|
|
34
|
+
if (defaultProviderRegistry.isOAuthProvider('github')) {
|
|
35
|
+
// Handle OAuth flow
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Map auth mode to consent provider type
|
|
39
|
+
const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
|
|
40
|
+
'credentials',
|
|
41
|
+
undefined
|
|
42
|
+
);
|
|
43
|
+
// Returns: 'credential'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Dependency Injection (Testing)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { ProviderRegistry, IProviderRegistry, createDefaultProviderRegistry } from '@kya-os/provider-registry';
|
|
50
|
+
|
|
51
|
+
// Create isolated registry for tests
|
|
52
|
+
const testRegistry = createDefaultProviderRegistry();
|
|
53
|
+
|
|
54
|
+
// Or create with custom providers
|
|
55
|
+
const customRegistry = new ProviderRegistry([
|
|
56
|
+
{
|
|
57
|
+
id: 'test-provider',
|
|
58
|
+
displayName: 'Test Provider',
|
|
59
|
+
authType: 'oauth2',
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
// Inject into your class
|
|
64
|
+
class MyAgent {
|
|
65
|
+
constructor(private providerRegistry: IProviderRegistry = defaultProviderRegistry) {}
|
|
66
|
+
|
|
67
|
+
handleAuth(provider: string) {
|
|
68
|
+
if (this.providerRegistry.isOAuthProvider(provider)) {
|
|
69
|
+
// OAuth flow
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Registering Custom Providers
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
79
|
+
|
|
80
|
+
// Register a custom credential provider (e.g., for enterprise auth)
|
|
81
|
+
defaultProviderRegistry.registerProvider({
|
|
82
|
+
id: 'my-company-auth',
|
|
83
|
+
displayName: 'My Company Login',
|
|
84
|
+
authType: 'password',
|
|
85
|
+
ui: {
|
|
86
|
+
description: 'Sign in with My Company credentials',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Register a custom OAuth provider
|
|
91
|
+
defaultProviderRegistry.registerProvider({
|
|
92
|
+
id: 'enterprise-sso',
|
|
93
|
+
displayName: 'Enterprise SSO',
|
|
94
|
+
authType: 'oauth2',
|
|
95
|
+
oauthProviderId: 'enterprise-sso',
|
|
96
|
+
defaultScopes: ['openid', 'profile', 'email'],
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Loading from Configuration
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
104
|
+
|
|
105
|
+
// Load from JSON config
|
|
106
|
+
const config = {
|
|
107
|
+
providers: [
|
|
108
|
+
{
|
|
109
|
+
id: 'my-provider',
|
|
110
|
+
displayName: 'My Provider',
|
|
111
|
+
authType: 'oauth2',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
defaultProviderRegistry.loadFromConfig(config);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Environment Variable Configuration
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Load from environment variable (JSON string)
|
|
123
|
+
const envConfig = process.env.MCP_PROVIDER_REGISTRY;
|
|
124
|
+
if (envConfig) {
|
|
125
|
+
const config = JSON.parse(envConfig);
|
|
126
|
+
defaultProviderRegistry.loadFromConfig(config);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Seal registry to prevent further modifications in production
|
|
130
|
+
defaultProviderRegistry.seal();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Finding Providers by OAuth Provider ID
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
137
|
+
|
|
138
|
+
// Find provider by oauthProviderId (useful when IDs differ)
|
|
139
|
+
defaultProviderRegistry.registerProvider({
|
|
140
|
+
id: 'custom-oauth',
|
|
141
|
+
authType: 'oauth2',
|
|
142
|
+
oauthProviderId: 'my-custom-oauth-id',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const provider = defaultProviderRegistry.findByOauthProviderId('my-custom-oauth-id');
|
|
146
|
+
// Returns: { id: 'custom-oauth', ... }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## API Reference
|
|
150
|
+
|
|
151
|
+
### `IProviderRegistry` Interface
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
interface IProviderRegistry {
|
|
155
|
+
getProvider(id: string): ProviderDefinition | undefined;
|
|
156
|
+
findByOauthProviderId(oauthProviderId: string): ProviderDefinition | undefined;
|
|
157
|
+
isKnownProvider(id: string): boolean;
|
|
158
|
+
isOAuthProvider(id: string): boolean;
|
|
159
|
+
isCredentialProvider(id: string): boolean;
|
|
160
|
+
mapAuthModeToConsentProvider(authMode?: AuthMode | string, oauthProviderId?: string): ConsentProviderType;
|
|
161
|
+
mapProviderToConsentProvider(providerId: string): ConsentProviderType;
|
|
162
|
+
determineProviderTypeFromAuthMode(authMode?: string, oauthIdentityProvider?: string): ConsentProviderType; // @deprecated
|
|
163
|
+
registerProvider(def: ProviderDefinition, opts?: { overwrite?: boolean }): void;
|
|
164
|
+
listProviders(): ProviderDefinition[];
|
|
165
|
+
loadFromConfig(config: unknown): void;
|
|
166
|
+
seal(): void;
|
|
167
|
+
isSealed(): boolean;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `ProviderDefinition` Type
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
interface ProviderDefinition {
|
|
175
|
+
id: string; // Unique identifier (e.g., 'github', 'my-company-auth')
|
|
176
|
+
displayName?: string; // Human-friendly name
|
|
177
|
+
authType: ProviderAuthType; // 'oauth2', 'password', 'verifiable_credential', etc.
|
|
178
|
+
oauthProviderId?: string; // OAuth provider ID (must be unique across registry)
|
|
179
|
+
defaultScopes?: string[]; // Default OAuth scopes
|
|
180
|
+
oauthConfig?: OAuthProviderConfig; // OAuth endpoint configuration
|
|
181
|
+
credentialConfig?: CredentialProviderConfig; // Credential flow configuration
|
|
182
|
+
ui?: {
|
|
183
|
+
icon?: string;
|
|
184
|
+
description?: string;
|
|
185
|
+
};
|
|
186
|
+
metadata?: Record<string, unknown>; // Custom metadata (secret names, not secrets!)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `OAuthProviderConfig` Type
|
|
191
|
+
|
|
192
|
+
Configuration for OAuth provider flows:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
interface OAuthProviderConfig {
|
|
196
|
+
authorizationEndpoint: string; // OAuth authorization URL
|
|
197
|
+
tokenEndpoint: string; // OAuth token URL
|
|
198
|
+
userInfoEndpoint?: string; // User info endpoint
|
|
199
|
+
defaultScopes?: string[]; // Default scopes to request
|
|
200
|
+
supportsPKCE?: boolean; // Whether PKCE is supported
|
|
201
|
+
requiresClientSecret?: boolean; // Whether client secret is required
|
|
202
|
+
tokenEndpointAuthMethod?: 'client_secret_post' | 'client_secret_basic';
|
|
203
|
+
responseType?: string; // OAuth response type (default: 'code')
|
|
204
|
+
grantType?: string; // OAuth grant type (default: 'authorization_code')
|
|
205
|
+
customParams?: Record<string, string>; // Custom OAuth params (audience, etc.)
|
|
206
|
+
authUrlTemplate?: string; // URL template with {placeholders}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### `CredentialProviderConfig` Type
|
|
211
|
+
|
|
212
|
+
Configuration for credential/password authentication:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface CredentialProviderConfig {
|
|
216
|
+
authEndpoint: string; // Endpoint to POST credentials
|
|
217
|
+
httpMethod?: 'POST' | 'PUT'; // HTTP method
|
|
218
|
+
contentType?: 'application/json' | 'application/x-www-form-urlencoded';
|
|
219
|
+
requestBodyTemplate?: {
|
|
220
|
+
identityField?: string; // Field name for email/username
|
|
221
|
+
passwordField?: string; // Field name for password
|
|
222
|
+
additionalFields?: Record<string, string>;
|
|
223
|
+
};
|
|
224
|
+
responseFields?: {
|
|
225
|
+
sessionTokenPath?: string; // JSON path to session token
|
|
226
|
+
userIdPath?: string; // JSON path to user ID
|
|
227
|
+
userEmailPath?: string; // JSON path to user email
|
|
228
|
+
userDisplayNamePath?: string; // JSON path to display name
|
|
229
|
+
expiresInPath?: string; // JSON path to token expiry
|
|
230
|
+
};
|
|
231
|
+
successCheck?: {
|
|
232
|
+
path?: string; // JSON path to check for success
|
|
233
|
+
expectedValue?: string | boolean;
|
|
234
|
+
};
|
|
235
|
+
useCookieSession?: boolean; // Whether to use cookies for session
|
|
236
|
+
cookieNames?: string; // Cookie names to extract
|
|
237
|
+
customHeaders?: Record<string, string>;
|
|
238
|
+
requiresCsrf?: boolean; // Whether CSRF token is required
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Mapping Semantics
|
|
243
|
+
|
|
244
|
+
### `mapAuthModeToConsentProvider(authMode?, oauthProviderId?)`
|
|
245
|
+
|
|
246
|
+
Deterministic precedence rules:
|
|
247
|
+
|
|
248
|
+
1. **Explicit authMode (non-oauth)** → Direct mapping
|
|
249
|
+
- `'credentials'`, `'password'` → `'credential'`
|
|
250
|
+
- `'magic-link'` → `'magic_link'`
|
|
251
|
+
- `'otp'` → `'otp'`
|
|
252
|
+
- `'verifiable_credential'`, `'idv'`, `'mdl'` → `'verifiable_credential'`
|
|
253
|
+
- `'consent-only'`, `'none'`, `''` → `'none'`
|
|
254
|
+
|
|
255
|
+
2. **If authMode === 'oauth' or oauthProviderId present** → Consult registry
|
|
256
|
+
- Known provider → Map based on provider's authType
|
|
257
|
+
- **Unknown provider → `'oauth2'` (with warning log)**
|
|
258
|
+
|
|
259
|
+
3. **Fallback** → `'none'`
|
|
260
|
+
|
|
261
|
+
### `mapProviderToConsentProvider(providerId)`
|
|
262
|
+
|
|
263
|
+
- Known provider → Map based on provider's authType
|
|
264
|
+
- **Unknown provider → `'none'` (with warning log)**
|
|
265
|
+
|
|
266
|
+
### Fallback Behavior Rationale
|
|
267
|
+
|
|
268
|
+
The different fallbacks are intentional:
|
|
269
|
+
|
|
270
|
+
- **`mapAuthModeToConsentProvider` with unknown oauthProviderId → `'oauth2'`**: The presence of an oauthProviderId strongly implies an OAuth flow, even if the provider isn't registered.
|
|
271
|
+
|
|
272
|
+
- **`mapProviderToConsentProvider` with unknown providerId → `'none'`**: Direct provider lookup should fail safely without assumptions.
|
|
273
|
+
|
|
274
|
+
Both log warnings to help identify configuration issues.
|
|
275
|
+
|
|
276
|
+
## Default Providers
|
|
277
|
+
|
|
278
|
+
The registry comes pre-populated with common providers:
|
|
279
|
+
|
|
280
|
+
**OAuth Providers**: GitHub, Google, Microsoft, Discord, Slack, Apple, LinkedIn, Twitter, Facebook, Okta, Auth0
|
|
281
|
+
|
|
282
|
+
**Credential Provider**: `credentials` (generic placeholder)
|
|
283
|
+
|
|
284
|
+
**Note**: Customer-specific providers (like HardwareWorld) should NOT be in default providers. Register them via:
|
|
285
|
+
- `registerProvider()` at runtime
|
|
286
|
+
- `loadFromConfig()` with custom configuration
|
|
287
|
+
- `MCP_PROVIDER_REGISTRY` environment variable
|
|
288
|
+
|
|
289
|
+
## Security: Secrets Handling
|
|
290
|
+
|
|
291
|
+
> ⚠️ **NEVER store actual secrets in provider definitions.** Store only secret references (names).
|
|
292
|
+
|
|
293
|
+
Provider definitions may contain endpoint URLs and configuration, but **client secrets, API keys, and other sensitive credentials must NOT be stored in the registry**.
|
|
294
|
+
|
|
295
|
+
### The Correct Approach
|
|
296
|
+
|
|
297
|
+
Store **secret names** in `metadata` and read actual secrets from secure storage at runtime:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// ✅ CORRECT: Store secret name, not secret value
|
|
301
|
+
defaultProviderRegistry.registerProvider({
|
|
302
|
+
id: 'enterprise-oauth',
|
|
303
|
+
displayName: 'Enterprise SSO',
|
|
304
|
+
authType: 'oauth2',
|
|
305
|
+
oauthConfig: {
|
|
306
|
+
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
|
|
307
|
+
tokenEndpoint: 'https://sso.example.com/oauth/token',
|
|
308
|
+
},
|
|
309
|
+
metadata: {
|
|
310
|
+
clientSecretName: 'ENTERPRISE_SSO_CLIENT_SECRET', // Name, not value!
|
|
311
|
+
clientId: 'my-app-client-id', // Client ID is often public, OK to store
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// At runtime, read secret from secure storage
|
|
316
|
+
async function getProviderSecret(providerId: string): Promise<string> {
|
|
317
|
+
const provider = defaultProviderRegistry.getProvider(providerId);
|
|
318
|
+
const secretName = provider?.metadata?.clientSecretName as string;
|
|
319
|
+
|
|
320
|
+
// Read from Cloudflare Secrets, AWS Secrets Manager, vault, etc.
|
|
321
|
+
return env[secretName] || await secretsManager.getSecret(secretName);
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### What Can Be Stored in Registry
|
|
326
|
+
|
|
327
|
+
| Field | OK to Store | Notes |
|
|
328
|
+
|-------|-------------|-------|
|
|
329
|
+
| `authorizationEndpoint` | ✅ Yes | Public URL |
|
|
330
|
+
| `tokenEndpoint` | ✅ Yes | Public URL |
|
|
331
|
+
| `clientId` | ✅ Usually | Often public (check your provider) |
|
|
332
|
+
| `clientSecret` | ❌ **NO** | Store as `metadata.clientSecretName` |
|
|
333
|
+
| `apiKey` | ❌ **NO** | Store as `metadata.apiKeyName` |
|
|
334
|
+
| `defaultScopes` | ✅ Yes | Configuration, not secret |
|
|
335
|
+
|
|
336
|
+
### Credential Provider Security
|
|
337
|
+
|
|
338
|
+
For password/credential flows, the `credentialConfig` describes **how** to authenticate, not **with what credentials**:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// ✅ CORRECT: Describe flow structure, not credentials
|
|
342
|
+
defaultProviderRegistry.registerProvider({
|
|
343
|
+
id: 'legacy-api',
|
|
344
|
+
authType: 'password',
|
|
345
|
+
credentialConfig: {
|
|
346
|
+
authEndpoint: 'https://api.example.com/auth/login',
|
|
347
|
+
contentType: 'application/json',
|
|
348
|
+
requestBodyTemplate: {
|
|
349
|
+
identityField: 'email', // Field name, not value
|
|
350
|
+
passwordField: 'password', // Field name, not value
|
|
351
|
+
},
|
|
352
|
+
responseFields: {
|
|
353
|
+
sessionTokenPath: 'data.token',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
metadata: {
|
|
357
|
+
// If the API requires static headers like API keys:
|
|
358
|
+
apiKeyHeaderName: 'X-API-Key',
|
|
359
|
+
apiKeySecretName: 'LEGACY_API_KEY', // Read from env/secrets at runtime
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Production Best Practices
|
|
365
|
+
|
|
366
|
+
### 1. Load Custom Providers Early
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
370
|
+
|
|
371
|
+
// In your server initialization
|
|
372
|
+
async function initializeRegistry() {
|
|
373
|
+
// Load from config/environment
|
|
374
|
+
if (process.env.MCP_PROVIDER_REGISTRY) {
|
|
375
|
+
defaultProviderRegistry.loadFromConfig(
|
|
376
|
+
JSON.parse(process.env.MCP_PROVIDER_REGISTRY)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Seal to prevent runtime modifications
|
|
381
|
+
defaultProviderRegistry.seal();
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 2. Use Dependency Injection for Testing
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import { createDefaultProviderRegistry, IProviderRegistry } from '@kya-os/provider-registry';
|
|
389
|
+
|
|
390
|
+
class MyService {
|
|
391
|
+
constructor(private registry: IProviderRegistry = defaultProviderRegistry) {}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// In tests
|
|
395
|
+
const testRegistry = createDefaultProviderRegistry();
|
|
396
|
+
testRegistry.registerProvider({ id: 'mock', authType: 'oauth2' });
|
|
397
|
+
const service = new MyService(testRegistry);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 3. Custom Logger for Metrics
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
import { createDefaultProviderRegistry, RegistryLogger } from '@kya-os/provider-registry';
|
|
404
|
+
|
|
405
|
+
const metricsLogger: RegistryLogger = {
|
|
406
|
+
warn(message, context) {
|
|
407
|
+
myMetricsClient.increment('provider_registry.warning', {
|
|
408
|
+
message,
|
|
409
|
+
...context,
|
|
410
|
+
});
|
|
411
|
+
console.warn(message, context);
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const registry = createDefaultProviderRegistry(metricsLogger);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 4. Registry Lifecycle & Multi-tenant Deployments
|
|
419
|
+
|
|
420
|
+
**Lifecycle expectations for the singleton:**
|
|
421
|
+
|
|
422
|
+
1. **Initialization**: The `defaultProviderRegistry` is lazily created on first access (no module-load side effects).
|
|
423
|
+
|
|
424
|
+
2. **Configuration**: Load project-specific providers early in your application startup:
|
|
425
|
+
```typescript
|
|
426
|
+
// At server/worker startup
|
|
427
|
+
if (env.MCP_PROVIDER_REGISTRY) {
|
|
428
|
+
defaultProviderRegistry.loadFromConfig(JSON.parse(env.MCP_PROVIDER_REGISTRY));
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
3. **Sealing**: Call `seal()` after configuration to prevent accidental runtime modifications:
|
|
433
|
+
```typescript
|
|
434
|
+
defaultProviderRegistry.seal();
|
|
435
|
+
// After this, registerProvider() and loadFromConfig() will throw
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
4. **Runtime**: Use the sealed registry for lookups throughout the application lifecycle.
|
|
439
|
+
|
|
440
|
+
**Multi-tenant deployments:**
|
|
441
|
+
|
|
442
|
+
For multi-tenant scenarios where different projects need different providers, **prefer per-project registry instances** instead of mutating the global singleton:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { ProviderRegistry, defaultProviders } from '@kya-os/provider-registry';
|
|
446
|
+
|
|
447
|
+
function createProjectRegistry(projectConfig: ProviderConfig): ProviderRegistry {
|
|
448
|
+
// Start with default providers
|
|
449
|
+
const registry = new ProviderRegistry(defaultProviders);
|
|
450
|
+
|
|
451
|
+
// Add project-specific providers
|
|
452
|
+
registry.loadFromConfig(projectConfig);
|
|
453
|
+
|
|
454
|
+
// Seal and return
|
|
455
|
+
registry.seal();
|
|
456
|
+
return registry;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Each tenant gets their own isolated registry
|
|
460
|
+
const tenant1Registry = createProjectRegistry(tenant1Config);
|
|
461
|
+
const tenant2Registry = createProjectRegistry(tenant2Config);
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Testing:**
|
|
465
|
+
|
|
466
|
+
Use `resetDefaultProviderRegistryForTests()` in test setup to ensure isolation:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { resetDefaultProviderRegistryForTests } from '@kya-os/provider-registry';
|
|
470
|
+
|
|
471
|
+
beforeEach(() => {
|
|
472
|
+
resetDefaultProviderRegistryForTests();
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Integration Examples
|
|
477
|
+
|
|
478
|
+
### Cloudflare Agent
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { defaultProviderRegistry, IProviderRegistry } from '@kya-os/provider-registry';
|
|
482
|
+
|
|
483
|
+
export class MCPICloudflareAgent {
|
|
484
|
+
constructor(
|
|
485
|
+
private providerRegistry: IProviderRegistry = defaultProviderRegistry
|
|
486
|
+
) {}
|
|
487
|
+
|
|
488
|
+
async handleOAuthRequired(error: OAuthRequiredError) {
|
|
489
|
+
const provider = error.provider?.toLowerCase() || '';
|
|
490
|
+
|
|
491
|
+
if (this.providerRegistry.isCredentialProvider(provider)) {
|
|
492
|
+
// Build credential consent URL
|
|
493
|
+
} else if (this.providerRegistry.isOAuthProvider(provider)) {
|
|
494
|
+
// Build OAuth URL
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Consent UI
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
504
|
+
|
|
505
|
+
const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
|
|
506
|
+
authMode,
|
|
507
|
+
oauthIdentity?.provider
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const provider = defaultProviderRegistry.getProvider(providerId);
|
|
511
|
+
if (provider?.ui?.icon) {
|
|
512
|
+
// Render provider icon
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Testing
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
import { ProviderRegistry, createDefaultProviderRegistry } from '@kya-os/provider-registry';
|
|
520
|
+
|
|
521
|
+
describe('MyComponent', () => {
|
|
522
|
+
it('should handle OAuth providers', () => {
|
|
523
|
+
const registry = new ProviderRegistry([
|
|
524
|
+
{ id: 'github', displayName: 'GitHub', authType: 'oauth2' },
|
|
525
|
+
]);
|
|
526
|
+
|
|
527
|
+
expect(registry.isOAuthProvider('github')).toBe(true);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should use isolated registry', () => {
|
|
531
|
+
const registry = createDefaultProviderRegistry();
|
|
532
|
+
registry.registerProvider({ id: 'custom', authType: 'password' });
|
|
533
|
+
|
|
534
|
+
// Won't affect other tests or global singleton
|
|
535
|
+
expect(registry.isCredentialProvider('custom')).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Migration Guide
|
|
541
|
+
|
|
542
|
+
### Replacing Hardcoded Lists
|
|
543
|
+
|
|
544
|
+
**Before:**
|
|
545
|
+
```typescript
|
|
546
|
+
const KNOWN_OAUTH_PROVIDERS = ['github', 'google', 'microsoft'];
|
|
547
|
+
const isOAuth = KNOWN_OAUTH_PROVIDERS.includes(provider);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**After:**
|
|
551
|
+
```typescript
|
|
552
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
553
|
+
const isOAuth = defaultProviderRegistry.isOAuthProvider(provider);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Replacing Provider Type Detection
|
|
557
|
+
|
|
558
|
+
**Before:**
|
|
559
|
+
```typescript
|
|
560
|
+
function getProviderType(authMode?: string, oauthProvider?: string) {
|
|
561
|
+
if (authMode === 'credentials') return 'credential';
|
|
562
|
+
if (oauthProvider) return 'oauth2';
|
|
563
|
+
return 'none';
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**After:**
|
|
568
|
+
```typescript
|
|
569
|
+
import { defaultProviderRegistry } from '@kya-os/provider-registry';
|
|
570
|
+
const providerType = defaultProviderRegistry.mapAuthModeToConsentProvider(
|
|
571
|
+
authMode,
|
|
572
|
+
oauthProvider
|
|
573
|
+
);
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Using findByOauthProviderId
|
|
577
|
+
|
|
578
|
+
**Before:**
|
|
579
|
+
```typescript
|
|
580
|
+
// Manual lookup with potential mismatch
|
|
581
|
+
const provider = registry.getProvider(oauthIdentityProvider);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**After:**
|
|
585
|
+
```typescript
|
|
586
|
+
// Proper lookup using oauthProviderId index
|
|
587
|
+
const provider = registry.findByOauthProviderId(oauthIdentityProvider);
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## License
|
|
591
|
+
|
|
592
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Provider Definitions
|
|
3
|
+
*
|
|
4
|
+
* Pre-populated list of common authentication providers.
|
|
5
|
+
* This list is intentionally concise - additional providers should be
|
|
6
|
+
* registered via config or runtime registration.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: Custom/customer-specific providers (e.g., HardwareWorld) should NOT be
|
|
9
|
+
* in this list. They should be registered dynamically via:
|
|
10
|
+
* - loadFromConfig() with custom configuration
|
|
11
|
+
* - registerProvider() at runtime
|
|
12
|
+
* - MCP_PROVIDER_REGISTRY environment variable
|
|
13
|
+
*/
|
|
14
|
+
import type { ProviderDefinition } from './types';
|
|
15
|
+
/**
|
|
16
|
+
* Default set of common providers
|
|
17
|
+
*
|
|
18
|
+
* These providers are automatically registered when creating a new ProviderRegistry
|
|
19
|
+
* without explicit initial providers.
|
|
20
|
+
*
|
|
21
|
+
* Includes:
|
|
22
|
+
* - Well-known OAuth providers (GitHub, Google, Microsoft, etc.)
|
|
23
|
+
* - Generic credential provider placeholder
|
|
24
|
+
*
|
|
25
|
+
* Does NOT include:
|
|
26
|
+
* - Customer-specific providers (should be loaded via config)
|
|
27
|
+
* - Custom OAuth providers (should be registered at runtime)
|
|
28
|
+
*/
|
|
29
|
+
export declare const defaultProviders: ProviderDefinition[];
|
|
30
|
+
//# sourceMappingURL=default-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-providers.d.ts","sourceRoot":"","sources":["../src/default-providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,EAAE,kBAAkB,EAqIhD,CAAC"}
|