33800 Docs

← Retour

Connectors Hub - Auto-Discovery

Date : 25/01/2026 Status : PROPOSITION Projet : connectors-api


Problème actuel

  1. La création d'un connecteur ne déclenche PAS la découverte automatique
  2. La discovery ne fait que des GET → pas de request_schema pour POST/PUT
  3. Pas de recherche automatique du swagger de l'API
  4. L'utilisateur doit manuellement appeler /api/discover/:id

Solution proposée

1. Auto-discovery à la création

Quand un connecteur est créé :

POST /api/connectors → create() → triggerAutoDiscovery()

Le flow :

  1. Si swagger_url fourni → importer directement
  2. Sinon → chercher le swagger aux URLs communes
  3. Si trouvé → importer
  4. Sinon → explorer les endpoints communs (optionnel)

2. Recherche intelligente du swagger

Essayer ces URLs dans l'ordre :

const SWAGGER_PATHS = [
  '/swagger.json',
  '/openapi.json',
  '/api-docs',
  '/v3/api-docs',
  '/swagger/v1/swagger.json',
  '/api/swagger.json',
  '/.well-known/openapi.json',
  '/docs/openapi.json',
];

3. Nouveau service : autodiscovery.ts

export const autodiscovery = {
  /**
   * Find swagger URL for a connector
   */
  async findSwaggerUrl(baseUrl: string): Promise<string | null> {
    for (const path of SWAGGER_PATHS) {
      const url = `${baseUrl}${path}`;
      try {
        const res = await fetch(url, { method: 'HEAD' });
        if (res.ok) {
          // Verify it's JSON
          const contentType = res.headers.get('content-type');
          if (contentType?.includes('json')) {
            return url;
          }
        }
      } catch {}
    }
    return null;
  },

  /**
   * Trigger full discovery for a connector
   */
  async triggerDiscovery(
    connector: ConnectorConfig,
    options?: {
      userId?: string;
      skipIfExists?: boolean;
      exploreFallback?: boolean;
    }
  ): Promise<DiscoveryResult> {
    // 1. Check if already discovered
    if (options?.skipIfExists) {
      const existing = await discovery.getEndpoints(connector.id);
      if (existing.length > 0) {
        return { status: 'skipped', message: 'Already discovered', count: existing.length };
      }
    }

    // 2. Try swagger_url from config
    if (connector.swagger_url) {
      try {
        const count = await discovery.importOpenAPIFromUrl(connector, connector.swagger_url);
        return { status: 'success', source: 'swagger_url', count };
      } catch (e) {
        console.warn(`Failed to import from swagger_url: ${e}`);
      }
    }

    // 3. Search for swagger
    const foundUrl = await this.findSwaggerUrl(connector.base_url);
    if (foundUrl) {
      try {
        const count = await discovery.importOpenAPIFromUrl(connector, foundUrl);
        // Save found URL
        await registry.update(connector.id, { swagger_url: foundUrl });
        return { status: 'success', source: 'auto_detected', swagger_url: foundUrl, count };
      } catch (e) {
        console.warn(`Failed to import from detected swagger: ${e}`);
      }
    }

    // 4. Fallback: explore common endpoints
    if (options?.exploreFallback) {
      const discovered = await this.exploreCommonEndpoints(connector, options.userId);
      return { status: 'partial', source: 'exploration', count: discovered.length };
    }

    return { status: 'failed', message: 'No swagger found and exploration disabled' };
  },

  /**
   * Explore common endpoints when no swagger available
   */
  async exploreCommonEndpoints(connector: ConnectorConfig, userId?: string): Promise<string[]> {
    const commonPaths = [
      '/user', '/users', '/me',
      '/projects', '/repos', '/repositories',
      '/issues', '/items', '/resources',
      '/organizations', '/orgs', '/teams',
      '/domains', '/accounts', '/contacts'
    ];

    const discovered: string[] = [];
    for (const path of commonPaths) {
      try {
        await discovery.discoverEndpoint(connector, 'GET', path, userId, 1);
        discovered.push(path);
      } catch {}
    }
    return discovered;
  }
};

4. Modifier registry.create()

async create(config: Partial<ConnectorConfig>): Promise<ConnectorConfig> {
  // Create connector
  const connector = await supabase.insert<ConnectorConfig>('config', {...});

  // Trigger auto-discovery in background (don't block)
  autodiscovery.triggerDiscovery(connector, {
    exploreFallback: true
  }).then(result => {
    console.log(`Auto-discovery for ${connector.name}:`, result);
  }).catch(e => {
    console.error(`Auto-discovery failed for ${connector.name}:`, e);
  });

  return connector;
}

5. Nouveau champ : discovery_status

Ajouter au type ConnectorConfig :

discovery_status: 'pending' | 'in_progress' | 'completed' | 'failed' | null;
discovery_error: string | null;
endpoints_count: number;

6. Endpoint pour forcer re-discovery

POST /api/connectors/:id/rediscover

Force une nouvelle découverte (efface les endpoints existants).


Fichiers à modifier

Fichier Modification
src/types/connector.ts Ajouter discovery_status, discovery_error, endpoints_count
src/services/autodiscovery.ts NOUVEAU - Service auto-discovery
src/services/registry.ts Appeler autodiscovery.triggerDiscovery() dans create()
src/index.ts Ajouter endpoint /api/connectors/:id/rediscover

Swaggers connus pour les connecteurs existants

Connecteur Swagger URL Taille Notes
GitHub github.com/rest-api-description 11 Mo Nécessite extraction
GitLab gitlab.com/api/v4 swagger ~5 Mo OpenAPI 3.0
Mailjet dev.mailjet.com/email/reference N/A Pas d'OpenAPI public

Décision attendue

  1. Valider l'approche auto-discovery à la création
  2. Confirmer le fallback exploration si pas de swagger
  3. Prioriser les connecteurs à enrichir (GitHub, GitLab, Mailjet)