Site
全記事

公開日 2026年9月23日約9分で読了

AI Connect SDK:AIエージェントに、各ユーザーのコネクタをツールとして渡す、フレームワークごとの1行

AIプロダクトの統合コストは配線であり、モデルではありません。Vinkius AI Connect SDKがそれを取り除きます。ランタイム依存ゼロのTypeScriptクライアント、ユーザー単位のスコープ付きハンドル、9つのフレームワークアダプタ。仕組み、最小コード、本番で使えるマルチユーザーアシスタントまで。

Renato Marinho

著者 Renato Marinho

Founder · Vinkius

The Vinkius AI Connect SDK: your app passes an app id, a user's external id and an intent; the SDK provisions, resolves and executes that user's capabilities at the governed MCP data plane; nine framework adapters turn the result into LLM tools; zero runtime dependencies

AIプロダクトについて私が最も多く受ける質問は、モデルについてではなく、配線についてです。あなたのアシスタントは利用者のGitHubをどうやって読むのか。Slackにどう投稿するのか。CRMからどうレコードを取得するのか。ほとんどのプロダクトでの答えは、チームが何ヶ月もかけて統合の棚を作り、保ってきた、ということです。OAuthの手順、トークンのローテーション、ベンダーごとのエラー形状、リトライ、上流APIが変わった瞬間のsilentなスキーマドリフト。これらのどれひとつとして、プロダクトを賢くしません。モデルと利用者が本当にやりたかったことの間に入って、予算を求め続けています。

Vinkius AI Connect SDKは、まさにこのコストを取り除くためのものです。営業では最も話さず、エンジニアには最も話すVinkiusの部分。実際のレバレッジはここにあり、小さなTypeScriptクライアントひとつで、利用者が接続したサービスを、あなたのモデルが呼び出せるツールに変えられる。あなたのチームが上流の認証情報をひとつたりとも保有しなくてよい状態です。

1行で SDK をまとめる

@vinkius/connect は、Vinkiusのコネクティビティプラットフォーム用のランタイム依存ゼロのTypeScriptクライアントです。あなたのアプリケーションは、Vinkiusダッシュボードで一度だけ作成するキーのペアで識別されます。公開のアプリIDと秘密のアプリケーションキーです。そして、あなたのシステムですでに利用者に割り当てているIDで、エンドユーザーを指定します。その各利用者に、SDKは彼らが接続したコネクタへのスコープ付きハンドルを渡します。Vinkiusが接続をプロビジョニングし、認証情報を保持し、ガバナンスされたデータプレーンの背後で機能を実行します。あなたのコードには、上流のAPIキーも、OAuthフローも、第三者APIのリトライループも持ちません。あなたのモデルの固有のツール方言で作業を記述し、アダプタがその利用者にとって正しいコネクタにルーティングします。

この一文が、価値提案のすべてです。この記事の残りは、その証明です。

モデル:1つのアプリ、多数のユーザー、ユーザー単位のスコープ

最も強く弁明する設計判断は、スコープの単位はユーザーでありテナントではない、ということです。ある人のカレンダーに操作できるチャットアシスタントは、触れるカレンダーがその人が接続したもので、それだけであるときにのみ安全です。SDKはこれを直接エンコードしています。vinkius.user("yourUserId") は、ネットワーク呼び出しをゼロにし、内部のユーザーIDを解決も保存もしない、遅延ハンドルを構築します。あなたの外部IDでプラットフォームを指し、実行まで貫きます。

各ユーザーのコネクタは、固有のトークンを持つ隔離された接続です。機能の列挙と実行はその接続のランタイムで行われるため、あるユーザーの接続したGitHubが別のユーザーのGitHubに到達することはありません。各接続はメーターリングされ、独立して止められ、取り消されたトークンは静かに再発行するのではなく失敗閉塞します。利用者がサービスを解除すると、次のリクエストで機能がそのユーザーのツールセットから消えます。分離はドキュメント上の約束ではなく、データパスの形です。

コード、3つの動き

ここが接続と認証情報のフローで、SDK自身のサンプルからそのままです。ユーザーのGitHubコネクタを接続し、期待されるスキーマを読み、トークンを書き、レディネスを確認します。

import { Vinkius } from '@vinkius/connect';

const vinkius = new Vinkius({
  appId: process.env.VINKIUS_APP_ID!,
  apiKey: process.env.VINKIUS_APP_KEY!,
});

async function connectGithub(userId: string, githubToken: string) {
  const github = vinkius.user(userId).connector('github');

  await github.connect();
  const schema = await github.credentials.schema();
  await github.credentials.set({ GITHUB_TOKEN: githubToken });

  const status = await github.status();
  console.log(`github status=${status} requires=${Object.keys(schema).join(',')}`);
}

status は4つの言葉のいずれかとして返ります。not_connectedneeds_credentialsreadydisabled。認証情報は書き込み専用です。credentials.schema()credentials.status() が存在するキーと設定済みのキーを知らせてくれますが、値は決して知らせてくれません。秘密がたどる唯一の経路は、一度だけ、vault 内側に向かうことです。

2つめの動きは集約です。1つの呼び出しで、そのユーザーのレディな全コネクタの実行可能な機能が、2つのコネクタが同時に create ツールを持つところで衝突しないよう、名前空間付きで返ります。

const caps = await vinkius.user('alice_123').capabilities();
// caps is a CapabilitySet: an array of executable Capability objects

3つめの動きはアダプタです。どのモデルを動かしても同じ形のコードになる、この1行こそがプラットフォームの対価です。

import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';

const tools = toOpenAITools(caps);
// pass tools to your model, then dispatch each call back:
const result = await runOpenAIToolCall(caps, call, { idempotencyKey: `alice_123:${call.id}` });

モデルを変えれば、import も変えるだけ。主なモデル向けフレームワークのためのサブパスアダプタは9つあります。OpenAIとAnthropicのSDKから、LangChain、LlamaIndex、Vercel AI SDK、OpenAI Agents、Cloudflare Workers AIまで、さらに残りのためのフレームワーク中立なJSON Schemaディスパッチャもあります。それぞれは依存ゼロの構造的型コンバータで、Vinkiusの機能をあなたのモデルのツール方言に変換し、逆も変換します。あえてベンダーSDKをimportしないため、Vinkiusをプロジェクトに加えしても、今は使っていないものは何も来ません。

動くマルチユーザーアシスタント

これは私が納品する実装です。そのユーザー自身のコネクタを使って、そのユーザーの代理人として答えるチャットハンドラです。ツールループを完備しています。モデルがツールを要求し、ガバナンスされたランタイムで実行し、結果を返し、ループに上限を設けて、挙動の悪いモデルが永遠に請求を続けられないようにします。

import OpenAI from 'openai';
import { Vinkius } from '@vinkius/connect';
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';

const openai = new OpenAI();
const vinkius = new Vinkius({
  appId: process.env.VINKIUS_APP_ID!,
  apiKey: process.env.VINKIUS_APP_KEY!,
});

// Any model your chosen provider serves. The integration is model agnostic:
// nothing below depends on a specific model name.
const MODEL = process.env.OPENAI_MODEL ?? 'your-model-id';

export async function assistantFor(userId: string, question: string) {
  // 1. What can this user's connectors actually do, right now?
  const caps = await vinkius.user(userId).capabilities();
  if (caps.length === 0) {
    return 'You have not connected a service yet. Connect one in the app and I can act on it.';
  }

  // 2. Hand the model this user's capabilities as its own tools.
  const messages = [{ role: 'user', content: question }];
  const tools = toOpenAITools(caps);

  // 3. The tool loop. The model picks, we execute at the runtime, we feed the result back.
  let reply;
  for (let step = 0; step < 4; step++) {
    reply = await openai.chat.completions.create({
      model: MODEL,
      messages,
      tools,
    });

    const msg = reply.choices[0]?.message;
    if (!msg || !msg.tool_calls || msg.tool_calls.length === 0) break;

    messages.push(msg);
    for (const call of msg.tool_calls) {
      // Tool level failures come back as isError results, not throws.
      // Transport level failures throw the SDK's typed error classes.
      const result = await runOpenAIToolCall(caps, call, {
        idempotencyKey: `${userId}:${call.id}`,
      });
      const text = result.content.map((c) => c.text).join('\n');
      messages.push({
        role: 'tool',
        tool_call_id: call.id,
        content: result.isError ? `The tool reported an error: ${text}` : text,
      });
    }
  }

  return reply?.choices[0]?.message?.content ?? '';
}

そのブロックには、素朴な実装が静かに間違えるポイントが4つあるので、指摘しておきます。

1つめ、機能リストは、リクエストごと、ユーザーごと、レディなコネクタからのみ取得されます。新しいサービスを接続したユーザーは、デプロイなしで次のターンにそれを手に入れ、1つを取り消したユーザーは同じタイミングで対応するツールを失います。信頼関係を超えて生き残るキャッシュは何もありません。

2つめ、実行呼び出しの idempotencyKey が、リトライを安全にします。SDKは一時的な応答をリトライしますが、副作用を発生させるPOSTは、キーを宣言した場合のみリトライされます。そうすると、リプレイはサーバー側で重複除去され、2重実行されません。上記のキーはユーザーとツール呼び出しIDで名前空間化されているため、同じコネクタを扱う2つのユーザーは衝突しません。

3つめ、ループの上限です。ツールの結果を読み、不十分と判断し、次のツールを呼び出すエージェントこそが、プロダクトが有用な仕事をしてくれる仕組みです。迷ったモデルが非常に長く歩き続ける仕組みでもあります。ステップに上限を設ければ、請求も破壊半径も限定でき、最終ターンのモデルのプレーンテキスト回答が、返すべきものです。

4つめ、エラーの区分です。ツールレベルで失敗した機能(ラップしたAPIがエラーを返した)は、isError: true の結果として返ります。モデルはそれを読み、回復し、別のツールを再試行し、ユーザーに失敗を説明できます。通信や認証の失敗は、SDKの型付きエラークラスのうちの1つをthrowします。失敗の形が2つ、ハンドラーも2つ、どちらもループ全体を落とさない。

別のフレームワークで動かしても、アダプタの行だけが変化します。Anthropicなら toAnthropicToolsrunAnthropicToolUse。LangChainならフレームワークの tool ファクトリを注入し、SDKはピア依存を一切持ちません。機能側は同一です。

なぜこの形で作られたのか

チームに課している規則に、私は何度も戻ります。SDKは、あなたのアプリが、見ることも取り消すこともできない秘密を保有する仕様にさせてはならない。形全体はそこから導かれます。認証情報は書き込み専用で暗号化され、プラットフォームは自分自身のオペレーターにも読ませません。接続トークンは1つのコネクタにスコープされ、HMACで認証され、平文は保存されません。そのため、漏洩の最悪ケースは、名前の付いた、取り消し可能な、1コネクタの資産であり、すべての資産への恒久的なパスではなりません。リトライは上限とジッターがあり、リクエストごとのタイムアウトが本文の読み取りと競合するため、止まったストリームがワーカーを吊るし下げできません。制御プレーンもデータプレーンも同じトランスポートオブジェクトが動かしているため、リトライ、タイムアウト、マスキングのセマンティクスが両者で乖離することがありません。

言語を和らげない1箇所があります。アプリケーションのキーはサーバーサイドに留めること。これはバックエンドクライアントです。公開のアプリIDと秘密キーはあなたの環境に属し、バンドルには属しません。設計は、あなたが渡すユーザーのスコープが、あなたのサーバーが呼び出し元であることを証明できるものであると前提しています。往復を省くのにブラウザへ送る誘惑があれば、問題は統合の作業から秘密の管理へ移り、それは不利な取引です。

納品される形

npm install @vinkius/connect。完全な型付きのESM/CJSデュアル、ツリーシェイカブル、Node 18以上と、fetch があればどんなランタイムでも動きます。監査すべきランタイム依存も、ロックファイルの肥大も、セキュリティチームと議論すべき推移的サプライチェーンもありません。ライセンスはApache 2.0なので、コードはあなたのものとして、あなたのアプリが動くどこででも保持・実行できます。

それを動かす機能層は機能層とカタログのポストで、依存する実行保証はV8 isolateでの非信頼MCPサーバー実行のポストでカバーされています。この記事はその両者の開発者向け表面、あなたのチームが書く相手となるクライアントです。

統合プロジェクトではなく、機能を納品する。それがすべてであり、SDKの役割はひとつです。利用者が接続したツールを、あなたのモデルがすでに話す方言でモデルに渡し、邪魔をしないこと。

トピックconnectorscapabilitiesmcpagentssdk