Connectors와 웹 검색으로 퀵스타트 생성기 만들기

Connectors와 웹 검색으로 퀵스타트 생성기 만들기 (TypeScript)

Mistral 커넥터(Connectors)와 웹 검색(Web Search) 도구를 활용해 Polars 라이브러리 퀵스타트를 자동 생성하는 TypeScript 스크립트를 만드는 쿡북이에요. 같은 프롬프트를 5가지 도구 구성으로 각각 실행해, 모델에 좋은 소스를 줄수록 출력 품질이 어떻게 달라지는지 비교합니다.

출처: 문서

본문

Polars(파이썬용 고속 DataFrame 라이브러리)를 배우고 싶다고 가정해요. 문서를 읽고, 블로그 글을 훑고, 직접 퀵스타트를 조립하는 대신 — 올바른 도구에 접근할 수 있는 Mistral에게 시킬 수 있어요.

이 스크립트는 같은 프롬프트를 5번 보내는데, 매번 도구 구성을 다르게 해서 모델에게 더 좋은 소스를 줄수록 출력 품질이 어떻게 좋아지는지 확인할 수 있어요:

단계 도구 모델이 접근할 수 있는 것
1 없음 훈련 데이터만
2 웹 검색 블로그 글, Stack Overflow, 릴리스 노트
3 Context7 커넥터 공식 Polars 문서
4 둘 다 문서 + 웹 — 모델이 하위 주제별로 최적의 소스를 고름
5 필터링된 커넥터 단일 문서 검색 도구 (리졸버 건너뜀)

API 상태: 이 스크립트는 client.beta.connectors, client.beta.agents, client.beta.conversations를 사용해요. 이들은 베타 엔드포인트라 변경될 수 있어요. 최신 API 레퍼런스는 Connectors 문서를 참고하세요.

이 튜토리얼의 Python 버전도 여기에서 볼 수 있어요.

사전 준비 (Prerequisites)

이 쿡북을 완료하려면 다음이 필요해요:

  • Node.js와 패키지 매니저 (npm, pnpm, yarn)
  • Mistral 계정과 API 키

환경 설정 (Environment setup)

설치 (Install)

다음 방법 중 하나로 Mistral TypeScript SDK와 .env 파일에서 API 키를 불러오는 데 필요한 dotenv를 설치하세요.

npm:

npm install @mistralai/mistralai dotenv

pnpm:

pnpm add @mistralai/mistralai dotenv

yarn:

yarn add @mistralai/mistralai dotenv

필요한 환경 변수 (Required environment variables)

윗낮에 Mistral API 키가 필요해요. Studio에서 API 키 섹션으로 이동해 Connector access scope로 Private and shared connectors를 선택하고 새 API 키를 만드세요.

프로젝트 루트에 .env를 만들고 Mistral API 키를 추가하세요:

MISTRAL_API_KEY=your-mistral-api-key

1단계 — 설정 (Setup)

프로젝트 디렉터리에 build-a-quickstart-generator.ts를 만드세요:

touch build-a-quickstart-generator.ts

파일을 열고 클라이언트, Context7 서버 URL, 두 프롬프트, 그리고 출력 텍스트를 추출하는 헬퍼 함수를 추가하세요. 나머지 단계들은 main 함수의 try와 finally 블록을 채워요.

PROMPT는 한 번 정의해 1~4단계에서 재사용해서 비교가 공정하도록 해요. 5단계는 다른, 더 집중된 프롬프트를 사용해 Polars 라이브러리를 미리 지정하므로 커넥터의 리졸버 도구가 필요 없어요.

import "dotenv/config";
import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: process.env.MISTRAL_API_KEY });

// Workaround: the Connectors beta API now returns `owner_type` as a string,
// but the SDK's Zod schema still expects a number. This wrapper catches the
// validation error and returns the raw (correctly shaped) response instead.
// Remove this once the SDK ships a fix.
async function createConnector(
  params: Parameters<typeof client.beta.connectors.create>[0],
) {
  try {
    return await client.beta.connectors.create(params);
  } catch (e: any) {
    if (e.rawValue && e.statusCode === 201) return e.rawValue;
    throw e;
  }
}

const CONTEXT7_URL = "https://mcp.context7.com/mcp";

const PROMPT =
  "Write a Polars quickstart for a developer who knows pandas. " +
  "Cover: installation, reading a CSV, filtering rows, groupby aggregation, " +
  "and lazy evaluation. End with 3 gotchas when migrating from pandas. " +
  "Include runnable code examples.";

const FILTERED_PROMPT =
  "Using the Polars documentation, explain lazy evaluation in Polars. " +
  "Cover: what LazyFrame is, how to build a lazy query with .lazy(), " +
  "how .collect() triggers execution, and when to prefer lazy over eager. " +
  "Include a runnable before/after code example.";

// Extract printable text from a conversation output entry.
function outputText(output: any): string {
  const content = output.content;
  if (typeof content === "string") return content;
  return (content as any[]).map((c) => c.text ?? "").join("");
}

async function main(): Promise<void> {
  let agentId: string | undefined;
  let connectorId: string | undefined;

  try {
    // Step 2 — Baseline: no tools
    // Step 3 — Web search
    // Step 4 — Context7 connector: create connector, create agent, stream
    // Step 5 — Both tools combined: update agent
    // Step 6 — Filtered connector: update agent
  } finally {
    // Cleanup — delete agent and connector
  }
}

main().catch(console.error);

2단계 — 기준선: 도구 없음 (Baseline: no tools)

우선 도구 없이 모델에게 Polars 퀵스타트를 생성하라고 요청해요. 모델은 훈련 데이터만 사용할 수 있으므로, 지식 컷오프 이후에 바뀐 것은 빠지거나 틀릴 수 있어요.

// Step 2 — Baseline: no tools를 다음으로 바꾸세요:

    // Step 2 — Baseline: no tools
    console.log("--- Step 1: Baseline (no tools) ---\n");
    const baseline = await client.beta.conversations.start({
      model: "mistral-medium-latest",
      inputs: [{ role: "user", content: PROMPT }],
    });
    for (const output of baseline.outputs ?? []) {
      if ((output as any).type === "message.output") {
        console.log(outputText(output));
      }
    }

내장 도구로 web_search를 추가하면 모델이 최신 정보(최근 블로그 글, Stack Overflow 답변, 릴리스 노트)를 가져올 수 있어요. 커넥터 설정은 필요 없어요.

1단계와 출력을 비교해보면 더 최신 문법과 커뮤니티 팁이 보일 거예요.

// Step 3 — Web search를 다음으로 바꾸세요:

    // Step 3 — Web search
    console.log("\n--- Step 2: Web search ---\n");
    const webSearch = await client.beta.conversations.start({
      model: "mistral-medium-latest",
      inputs: [{ role: "user", content: PROMPT }],
      tools: [{ type: "web_search" }],
    });
    for (const output of webSearch.outputs ?? []) {
      if ((output as any).type === "message.output") {
        console.log(outputText(output));
      }
    }

4단계 — Context7 커넥터: 커넥터 생성, 에이전트 생성, 스트리밍

Context7은 널리 쓰이는 오픈소스 라이브러리의 최신 문서를 제공하는 MCP 서버예요. 인증이 필요 없어서 좋은 첫 커넥터로 적합해요.

이 단계는 네 부분으로 나뉘어요:

  • Context7의 MCP 엔드포인트를 가리키는 커넥터 생성
  • 자격 증명 등록 (Context7은 공개라 비어 있지만 — 레코드는 존재해야 해요)
  • 도구를 나열해 커넥터가 무엇을 노출하는지 확인 (6단계에서 이름을 사용할 거예요)
  • 커넥터가 연결된 에이전트를 만들어 대화를 스트리밍 — conversations API를 직접 쓰는 대신 에이전트를 쓰는 이유는 커넥터 도구 호출(tool.execution.started, tool.execution.delta, tool.execution.done)을 실시간으로 볼 수 있기 때문이에요

// Step 4 — Context7 connector: create connector, create agent, stream을 다음으로 바꾸세요:

    // Step 4 — Context7 connector: create connector, create agent, stream
    console.log("\n--- Step 3: Context7 connector (official docs) ---\n");

    const connector = await createConnector({
      name: "quickstart_context7",
      description: "Context7 connector — library documentation lookup",
      server: CONTEXT7_URL,
      visibility: "private",
    });
    connectorId = connector.id;
    console.log(`Created: ${connector.name}  (id=${connector.id})`);

    await client.beta.connectors.createOrUpdateUserCredentials({
      connectorIdOrName: connector.name!,
      credentialsCreateOrUpdate: {
        name: `${connector.name}-default`,
        credentials: { headers: {} },
        isDefault: true,
      },
    });
    console.log(`Credentials registered for ${connector.name}`);

    const toolsList = await client.beta.connectors.listTools({
      connectorIdOrName: connector.name!,
    });
    let docToolName: string | undefined;
    console.log(`\nTools exposed by ${connector.name}:`);
    for (const tool of toolsList) {
      console.log(`  - ${tool.name}: ${tool.description}`);
      if (
        (tool.description ?? "").toLowerCase().includes("documentation") ||
        tool.name.toLowerCase().includes("doc")
      ) {
        docToolName = tool.name;
      }
    }
    if (docToolName) {
      console.log(`\nDoc-retrieval tool for Step 5: ${docToolName}`);
    }

    const agent = await client.beta.agents.create({
      name: "quickstart_context7_agent",
      model: "mistral-medium-latest",
      instructions:
        "You are a helpful programming assistant. " +
        "When asked about a library, always use the Context7 connector to look up " +
        "the official documentation before answering. Do not rely on training data alone.",
      tools: [{ type: "connector" as const, connectorId: connector.id }],
    });
    agentId = agent.id;
    console.log(`Agent ready: ${agent.name}  (id=${agent.id})\n`);

    let conversationId: string | undefined;
    const stream3 = await client.beta.conversations.startStream(
      {
        agentId: agent.id,
        inputs: [{ role: "user", content: PROMPT }],
      },
      { timeoutMs: 300_000 },
    );
    for await (const item of stream3) {
      const data = item.data;
      const eventType = (data as any).type;
      const name = (data as any).name ?? "";
      if (eventType === "conversation.response.started") {
        conversationId = (data as any).conversationId;
      } else if (eventType === "message.output.delta") {
        process.stdout.write(".");
      } else {
        console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
      }
    }

    const messages3 = await client.beta.conversations.getMessages({
      conversationId: conversationId!,
    });
    const lastOutput3 = [...(messages3.messages ?? [])]
      .reverse()
      .find((m) => (m as any).type === "message.output") as any;
    if (lastOutput3) {
      console.log("\n");
      console.log(outputText(lastOutput3));
    }

등록한 Connectors를 Studio에서 확인하세요.

5단계 — 두 도구 결합: 에이전트 업데이트 (Both tools combined: update agent)

이게 핵심이에요. 에이전트에 웹 검색과 Context7 커넥터를 모두 주도록 업데이트해요. 모델은 정확한 API 예제를 위해 공식 문서를, 커뮤니티 지식·마이그레이션 함정·최근 릴리스 노트를 위해 웹 결과를 가져올 수 있어요. 각 하위 주제에 어떤 소스를 쓸지 모델이 결정해요.

1~3단계와 비교해보면 — 결합 버전이 눈에 띄게 풍부해요.

// Step 5 — Both tools combined: update agent를 다음으로 바꾸세요:

    // Step 5 — Both tools combined: update agent
    console.log("\n--- Step 4: Web search + Context7 connector ---\n");

    await client.beta.agents.update({
      agentId: agent.id,
      updateAgentRequest: {
        tools: [
          { type: "web_search" },
          { type: "connector" as const, connectorId: connector.id },
        ],
      },
    });

    conversationId = undefined;
    const stream4 = await client.beta.conversations.startStream(
      {
        agentId: agent.id,
        inputs: [{ role: "user", content: PROMPT }],
      },
      { timeoutMs: 300_000 },
    );
    for await (const item of stream4) {
      const data = item.data;
      const eventType = (data as any).type;
      const name = (data as any).name ?? "";
      if (eventType === "conversation.response.started") {
        conversationId = (data as any).conversationId;
      } else if (eventType === "message.output.delta") {
        process.stdout.write(".");
      } else {
        console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
      }
    }

    const messages4 = await client.beta.conversations.getMessages({
      conversationId: conversationId!,
    });
    const lastOutput4 = [...(messages4.messages ?? [])]
      .reverse()
      .find((m) => (m as any).type === "message.output") as any;
    if (lastOutput4) {
      console.log("\n");
      console.log(outputText(lastOutput4));
    }

6단계 — 필터링된 커넥터: 에이전트 업데이트 (Filtered connector: update agent)

Context7은 여러 도구를 노출해요: 리졸버(이름에서 라이브러리 ID를 찾는 역할)와 문서 검색 도구(라이브러리 ID로 페이지를 가져오는 역할). 이미 라이브러리 ID를 안다면, toolConfiguration.include로 커넥터를 문서 검색 도구 하나로 제한해 리졸버를 건너뛸 수 있어요.

마지막으로 에이전트를 업데이트해 그 도구들을 하나의 필터링된 커넥터로 교체해요. 이 단계는 Polars 라이브러리를 미리 지정하는 더 집중된 프롬프트를 사용하므로 리졸버가 불필요하조.

// Step 6 — Filtered connector: update agent를 다음으로 바꾸세요:

    // Step 6 — Filtered connector: update agent
    if (docToolName) {
      console.log(
        `\n--- Step 5: Filtered connector (only ${docToolName}) ---\n`,
      );

      await client.beta.agents.update({
        agentId: agent.id,
        updateAgentRequest: {
          tools: [
            {
              type: "connector" as const,
              connectorId: connector.id,
              toolConfiguration: {
                include: [docToolName],
              },
            },
          ],
        },
      });

      conversationId = undefined;
      const stream5 = await client.beta.conversations.startStream(
        {
          agentId: agent.id,
          inputs: [{ role: "user", content: FILTERED_PROMPT }],
        },
        { timeoutMs: 300_000 },
      );
      for await (const item of stream5) {
        const data = item.data;
        const eventType = (data as any).type;
        const name = (data as any).name ?? "";
        if (eventType === "conversation.response.started") {
          conversationId = (data as any).conversationId;
        } else if (eventType === "message.output.delta") {
          process.stdout.write(".");
        } else {
          console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
        }
      }

      const messages5 = await client.beta.conversations.getMessages({
        conversationId: conversationId!,
      });
      const lastOutput5 = [...(messages5.messages ?? [])]
        .reverse()
        .find((m) => (m as any).type === "message.output") as any;
      if (lastOutput5) {
        console.log("\n");
        console.log(outputText(lastOutput5));
      }
    } else {
      console.log(
        "\nSkipped Step 5 — DOC_TOOL_NAME was not detected. Set it manually from the tool list in Step 3.",
      );
    }

정리 (Cleanup)

완료되면 에이전트와 커넥터를 삭제하세요. 3~5단계에서 하나의 에이전트를 재사용해(매번 도구만 업데이트) 정리할 에이전트는 하나뿐이에요.

finally 블록의 // Cleanup — delete agent and connector를 다음으로 바꾸세요:

    // Cleanup — delete agent and connector
    if (agentId) {
      await client.beta.agents.delete({ agentId });
      console.log(`\nAgent deleted: ${agentId}`);
    }
    if (connectorId) {
      const result = await client.beta.connectors.delete({
        connectorId,
      });
      console.log(`Connector deleted: ${(result as any).message}`);
    }

실행 (Run)

모든 단계를 넣은 뒤 스크립트를 실행하세요:

npx tsx build-a-quickstart-generator.ts

또는 tsx를 dev dependency로 설치했다면 npm start로 실행할 수 있어요.

전체 스크립트 (Complete script)

참고용으로 모든 단계를 합친 전체 스크립트는 다음과 같아요. 전체 프로젝트는 GitHub에서도 볼 수 있어요.

import "dotenv/config";
import { Mistral } from "@mistralai/mistralai";

const client = new Mistral({ apiKey: process.env.MISTRAL_API_KEY });

const CONTEXT7_URL = "https://mcp.context7.com/mcp";

const PROMPT =
  "Write a Polars quickstart for a developer who knows pandas. " +
  "Cover: installation, reading a CSV, filtering rows, groupby aggregation, " +
  "and lazy evaluation. End with 3 gotchas when migrating from pandas. " +
  "Include runnable code examples.";

const FILTERED_PROMPT =
  "Using the Polars documentation, explain lazy evaluation in Polars. " +
  "Cover: what LazyFrame is, how to build a lazy query with .lazy(), " +
  "how .collect() triggers execution, and when to prefer lazy over eager. " +
  "Include a runnable before/after code example.";

// Extract printable text from a conversation output entry.
function outputText(output: any): string {
  const content = output.content;
  if (typeof content === "string") return content;
  return (content as any[]).map((c) => c.text ?? "").join("");
}

async function main(): Promise<void> {
  let agentId: string | undefined;
  let connectorId: string | undefined;

  try {
    // Step 2 — Baseline: no tools
    console.log("--- Step 1: Baseline (no tools) ---\n");
    const baseline = await client.beta.conversations.start({
      model: "mistral-medium-latest",
      inputs: [{ role: "user", content: PROMPT }],
    });
    for (const output of baseline.outputs ?? []) {
      if ((output as any).type === "message.output") {
        console.log(outputText(output));
      }
    }

    // Step 3 — Web search
    console.log("\n--- Step 2: Web search ---\n");
    const webSearch = await client.beta.conversations.start({
      model: "mistral-medium-latest",
      inputs: [{ role: "user", content: PROMPT }],
      tools: [{ type: "web_search" }],
    });
    for (const output of webSearch.outputs ?? []) {
      if ((output as any).type === "message.output") {
        console.log(outputText(output));
      }
    }

    // Step 4 — Context7 connector: create connector, create agent, stream
    console.log("\n--- Step 3: Context7 connector (official docs) ---\n");

    const connector = await createConnector({
      name: "quickstart_context7",
      description: "Context7 connector — library documentation lookup",
      server: CONTEXT7_URL,
      visibility: "private",
    });
    connectorId = connector.id;
    console.log(`Created: ${connector.name}  (id=${connector.id})`);

    await client.beta.connectors.createOrUpdateUserCredentials({
      connectorIdOrName: connector.name!,
      credentialsCreateOrUpdate: {
        name: `${connector.name}-default`,
        credentials: { headers: {} },
        isDefault: true,
      },
    });
    console.log(`Credentials registered for ${connector.name}`);

    const toolsList = await client.beta.connectors.listTools({
      connectorIdOrName: connector.name!,
    });
    let docToolName: string | undefined;
    console.log(`\nTools exposed by ${connector.name}:`);
    for (const tool of toolsList) {
      console.log(`  - ${tool.name}: ${tool.description}`);
      if (
        (tool.description ?? "").toLowerCase().includes("documentation") ||
        tool.name.toLowerCase().includes("doc")
      ) {
        docToolName = tool.name;
      }
    }
    if (docToolName) {
      console.log(`\nDoc-retrieval tool for Step 5: ${docToolName}`);
    }

    const agent = await client.beta.agents.create({
      name: "quickstart_context7_agent",
      model: "mistral-medium-latest",
      instructions:
        "You are a helpful programming assistant. " +
        "When asked about a library, always use the Context7 connector to look up " +
        "the official documentation before answering. Do not rely on training data alone.",
      tools: [{ type: "connector" as const, connectorId: connector.id }],
    });
    agentId = agent.id;
    console.log(`Agent ready: ${agent.name}  (id=${agent.id})\n`);

    let conversationId: string | undefined;
    const stream3 = await client.beta.conversations.startStream(
      {
        agentId: agent.id,
        inputs: [{ role: "user", content: PROMPT }],
      },
      { timeoutMs: 300_000 },
    );
    for await (const item of stream3) {
      const data = item.data;
      const eventType = (data as any).type;
      const name = (data as any).name ?? "";
      if (eventType === "conversation.response.started") {
        conversationId = (data as any).conversationId;
      } else if (eventType === "message.output.delta") {
        process.stdout.write(".");
      } else {
        console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
      }
    }

    const messages3 = await client.beta.conversations.getMessages({
      conversationId: conversationId!,
    });
    const lastOutput3 = [...(messages3.messages ?? [])]
      .reverse()
      .find((m) => (m as any).type === "message.output") as any;
    if (lastOutput3) {
      console.log("\n");
      console.log(outputText(lastOutput3));
    }

    // Step 5 — Both tools combined: update agent
    console.log("\n--- Step 4: Web search + Context7 connector ---\n");

    await client.beta.agents.update({
      agentId: agent.id,
      updateAgentRequest: {
        tools: [
          { type: "web_search" },
          { type: "connector" as const, connectorId: connector.id },
        ],
      },
    });

    conversationId = undefined;
    const stream4 = await client.beta.conversations.startStream(
      {
        agentId: agent.id,
        inputs: [{ role: "user", content: PROMPT }],
      },
      { timeoutMs: 300_000 },
    );
    for await (const item of stream4) {
      const data = item.data;
      const eventType = (data as any).type;
      const name = (data as any).name ?? "";
      if (eventType === "conversation.response.started") {
        conversationId = (data as any).conversationId;
      } else if (eventType === "message.output.delta") {
        process.stdout.write(".");
      } else {
        console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
      }
    }

    const messages4 = await client.beta.conversations.getMessages({
      conversationId: conversationId!,
    });
    const lastOutput4 = [...(messages4.messages ?? [])]
      .reverse()
      .find((m) => (m as any).type === "message.output") as any;
    if (lastOutput4) {
      console.log("\n");
      console.log(outputText(lastOutput4));
    }

    // Step 6 — Filtered connector: update agent
    if (docToolName) {
      console.log(
        `\n--- Step 5: Filtered connector (only ${docToolName}) ---\n`,
      );

      await client.beta.agents.update({
        agentId: agent.id,
        updateAgentRequest: {
          tools: [
            {
              type: "connector" as const,
              connectorId: connector.id,
              toolConfiguration: {
                include: [docToolName],
              },
            },
          ],
        },
      });

      conversationId = undefined;
      const stream5 = await client.beta.conversations.startStream(
        {
          agentId: agent.id,
          inputs: [{ role: "user", content: FILTERED_PROMPT }],
        },
        { timeoutMs: 300_000 },
      );
      for await (const item of stream5) {
        const data = item.data;
        const eventType = (data as any).type;
        const name = (data as any).name ?? "";
        if (eventType === "conversation.response.started") {
          conversationId = (data as any).conversationId;
        } else if (eventType === "message.output.delta") {
          process.stdout.write(".");
        } else {
          console.log(`\n[${eventType}]${name ? ` ${name}` : ""}`);
        }
      }

      const messages5 = await client.beta.conversations.getMessages({
        conversationId: conversationId!,
      });
      const lastOutput5 = [...(messages5.messages ?? [])]
        .reverse()
        .find((m) => (m as any).type === "message.output") as any;
      if (lastOutput5) {
        console.log("\n");
        console.log(outputText(lastOutput5));
      }
    } else {
      console.log(
        "\nSkipped Step 5 — DOC_TOOL_NAME was not detected. Set it manually from the tool list in Step 3.",
      );
    }
  } finally {
    // Cleanup — delete agent and connector
    if (agentId) {
      await client.beta.agents.delete({ agentId });
      console.log(`\nAgent deleted: ${agentId}`);
    }
    if (connectorId) {
      const result = await client.beta.connectors.delete({
        connectorId,
      });
      console.log(`Connector deleted: ${(result as any).message}`);
    }
  }
}

main().catch(console.error);

요약 (Summary)

이 스크립트는 Mistral 대화에 도구를 추가할수록 출력 품질이 어떻게 점진적으로 좋아지는지 보여줘요 — 훈련 데이터만 쓰는 기준선에서부터, 웹 검색과 문서 커넥터를 거쳐, 가장 풍부한 결과를 내는 결합까지.

만든 것:

  • 도구를 추가할 때마다 개선되는 Polars 퀵스타트 생성기
  • 공식 라이브러리 문서를 가져오는 Context7 커넥터
  • 불필요한 커넥터 도구를 건너뛰는 필터링된 도구 구성

사용한 Mistral 기능:

  • 커넥터 (베타)
  • Conversations API (베타)
  • Agents API (베타) — 도구 실행 이벤트 스트리밍에 사용
  • 웹 검색 내장 도구
  • 도구 필터링 (toolConfiguration.include)

기타 서비스:

  • Context7 — 오픈소스 라이브러리 문서용 MCP 서버

더 알아보기 (Learn more)

  • Connectors 문서 — 커넥터 개념과 API 레퍼런스
  • Mistral Agents API — 도구 실행 이벤트 스트리밍을 포함한 에이전트
  • Context7 — 오픈소스 라이브러리 문서용 MCP 서버
  • Python 버전 퀵스타트 생성기 튜토리얼