L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

add-provider

add-provider

Add a new storage or service provider to the core package. Use when implementing a new backend for StorageService (e.g., a new database) or a new service provider (e.g., a new LLM backend).

Independent GitHub snapshot · Automated classification · Original documentation governs

Ccyanheads / mcp-ts-coreApache-2.0

Documentation de la Skill

GitHub

name: add-provider description: > Add a new storage or service provider to the core package. Use when implementing a new backend for StorageService (e.g., a new database) or a new service provider (e.g., a new LLM backend). metadata: author: cyanheads version: "1.2" audience: internal type: reference

Context

Providers implement interfaces defined in core. They are selected at runtime via config (e.g., STORAGE_PROVIDER_TYPE). Tier 3 providers lazy-load their dependencies to keep the core bundle small.

Providers live inside the package source tree — import the interface via relative path (e.g., import type { IStorageProvider } from '../core/IStorageProvider.js'), not via the package subpath exports (those are for consumers).

Provider interfaces

DomainInterface file
Storagesrc/storage/core/IStorageProvider.ts
LLMsrc/services/llm/core/ILlmProvider.ts
Speechsrc/services/speech/core/ISpeechProvider.ts

Read the relevant interface fully before implementing — each has distinct required members. ISpeechProvider in particular requires readonly name, readonly supportsTTS, readonly supportsSTT, and healthCheck() in addition to the capability methods; these flags drive routing in SpeechService.

File conventions

Provider file location and naming differ by domain:

  • Storage — nested subdirectory, camelCase directory name, PascalCase-suffixed provider file. Each provider gets its own subdirectory for the provider file plus any co-located types: src/storage/providers/{{providerName}}/{{providerName}}Provider.ts (e.g., src/storage/providers/inMemory/inMemoryProvider.ts, src/storage/providers/supabase/supabaseProvider.ts + supabase.types.ts)

  • LLM / Speech — flat directory, kebab-case with .provider.ts suffix: src/services/llm/providers/{{provider-name}}.provider.ts src/services/speech/providers/{{provider-name}}.provider.ts (e.g., src/services/llm/providers/openrouter.provider.ts, src/services/speech/providers/elevenlabs.provider.ts)

Steps

  1. Identify the provider interface — read the interface file for the target domain (see table above).

  2. Create the provider file following the file convention for its domain (see above).

  3. Implement the interface — all methods must be implemented. Storage providers build on src/storage/core/providerHelpers.ts rather than re-deriving what the existing providers share:

    • serializeValue / encodeEntries — the JSON guard every provider runs before it writes. serializeValue returns the value's JSON text and throws McpError(SerializationError) for a bigint, a cyclic reference, or a top-level undefined, function, or symbol. encodeEntries encodes a whole setMany batch first, so one bad value rejects the batch before anything is written. Store and read back the JSON form; never keep the caller's object.
    • getManyViaGet / setManyViaSet / deleteManyViaDelete — the batch methods as a parallel fan-out of single-key operations, for backends with no native batch API. Pass setManyViaSet the output of encodeEntries and a private per-key write. Calling this.set per entry would let a bad entry fail after the others have landed.
    • encodeEnvelope / decodeEnvelope — the TTL envelope for backends with no TTL of their own (R2, filesystem). encodeEnvelope takes the JSON text from serializeValue and returns the stored document. decodeEnvelope returns { kind: 'expired' } so the provider can delete on read, returns pre-envelope JSON as a plain value, and throws SyntaxError on invalid JSON so the provider can attach the key to the error it raises.
    • paginateSortedKeys — one list() page over an already-sorted key set, with the cursor for the page that follows.
    • escapeLikePattern — escapes %, _, and \ in a prefix before a SQL LIKE.
  4. Lazy-load dependencies if Tier 3:

    let _client: SomeClient | undefined;
    async function getClient(): Promise<SomeClient> {
      if (!_client) {
        const { SomeClient } = await import('some-package');
        _client = new SomeClient(/* config */);
      }
      return _client;
    }
    
  5. Register the provider — the registration point differs by domain:

    • Storage — two changes required:

      1. Add the new provider string to the z.enum for STORAGE_PROVIDER_TYPE in src/config/index.ts — without this, the config schema rejects the env var at runtime.
      2. Add a case to the switch in src/storage/core/storageFactory.ts inside createStorageProvider(). Import the new provider class at the top of that file.
    • Speech — two changes required:

      1. Add the new provider string literal to the provider union in SpeechProviderConfig (src/services/speech/types.ts, field provider).
      2. Add a case to the switch in createSpeechProvider() (src/services/speech/core/SpeechService.ts). Import the new provider class at the top of that file.
    • LLM — currently only one provider exists (OpenRouterProvider); it is instantiated directly in src/core/app.ts rather than through a factory switch. There is no factory pattern yet — adding a second provider requires introducing one (a selector env var, a factory function, and a conditional in app.ts). Read src/core/app.ts to understand the current instantiation site before designing the wiring.

  6. Update the Worker-compatible provider list if the new storage provider runs in Cloudflare Workers. The list is an inline array in storageFactory.ts at the isServerless() guard:

    // src/storage/core/storageFactory.ts
    !['in-memory', 'cloudflare-r2', 'cloudflare-kv', 'cloudflare-d1'].includes(providerType)
    

    Add the new provider string to this array. Non-storage providers have no equivalent gate.

  7. Add the dependency if Tier 3: add to both peerDependencies and peerDependenciesMeta (with { "optional": true }) in package.json. Without the peerDependenciesMeta entry, the dep appears required rather than optional.

  8. Run bun run rebuild — since this is package source, verify the build output compiles.

  9. Run bun run devcheck to verify.

Checklist

  • Provider file created with JSDoc @fileoverview + @module header
  • Interface fully implemented (including name, supportsTTS/supportsSTT for speech)
  • Tier 3 dependencies lazy-loaded (not top-level imports)
  • Registered in the correct factory for the domain (see Step 5)
  • Storage: provider string added to z.enum in src/config/index.ts
  • Storage: Worker-compatible array in storageFactory.ts updated if applicable
  • Storage: value encoding, batch, TTL envelope, paging, and LIKE escaping come from providerHelpers.ts, not a local copy
  • Storage: the shared compliance suite (tests/compliance/storage-provider.ts) runs against the provider
  • Speech: provider literal added to SpeechProviderConfig union in types.ts
  • LLM: src/core/app.ts instantiation logic updated if adding a second LLM provider
  • Optional peer dependency added to both peerDependencies and peerDependenciesMeta in package.json if Tier 3
  • bun run rebuild succeeds
  • bun run devcheck passes
  • Tests added under tests/unit/storage/providers/{{providerName}}/ (storage) or tests/unit/services/{{domain}}/providers/{{provider-name}}.provider.test.ts (LLM / speech), and bun run test passes
Skills enregistrées