התחברות לשרת ה-MCP של Developer Knowledge באמצעות ערכות ה-SDK של MCP

ערכות ה-SDK הרשמיות של Model Context Protocol (‏MCP) מאפשרות לאפליקציות ולסוכני ה-AI שלכם להתחבר לשרת ה-MCP של Developer Knowledge באופן פרוגרמטי. השתמשו ב-MCP SDK כשאתם יוצרים סוכן בהתאמה אישית, שירות לקצה העורפי או שרת MCP משלכם שצריך לחפש ולאחזר מאמרי עזרה רשמיים למפתחים של Google, במקום להגדיר IDE קיים או עוזר דיגיטלי לתכנות.

במדריך הזה תלמדו איך:

  • מתקינים את ערכת ה-SDK של לקוח MCP ל-Python או ל-TypeScript.
  • מתחברים לשרת ה-MCP של Developer Knowledge באמצעות מפתח API או Application Default Credentials ‏ (ADC).
  • מפעילים את הכלים search_documents,‏ get_documents ו-answer_query.
  • לטפל בשגיאות שקשורות לאימות, למכסות ולכלים.

לפני שמתחילים

לפני שמתחילים, צריך להשלים את הקטעים הבאים.

הפעלת ה-API ויצירת פרטי כניסה

מפעילים את Developer Knowledge API בפרויקט בענן ב-Google Cloud ויוצרים את פרטי הכניסה לשיטת האימות. הוראות מפורטות מופיעות במאמר בחירה של שיטת האימות.

בדוגמאות שבמדריך הזה נעשה שימוש במפתח API. כדי להשתמש ב-ADC, קראו את המאמר אימות באמצעות Application Default Credentials.

התקנת ה-SDK של MCP

בוחרים את שפת התכנות כדי להתקין את ערכת ה-SDK של לקוח ה-MCP:

Python

במדריך הזה נעשה שימוש בגרסה 2 של MCP Python SDK, שנדרשת בה Python 3.10 ואילך. בגרסה 1.x של ה-SDK נעשה שימוש בשמות שונים של מחלקות ופונקציות, ולכן הדוגמאות האלה לא פועלות בה:

pip install --upgrade "mcp>=2"

TypeScript

במדריך הזה משתמשים ב-MCP TypeScript SDK עם Node.js מגרסה 18 ואילך:

npm install @modelcontextprotocol/sdk

הגדרת מפתח ה-API

כדי להימנע מהטמעת מפתח ה-API בקוד המקור, כדאי לאחסן אותו במשתנה סביבה. מחליפים את הערך YOUR_API_KEY במפתח ה-API שלכם:

export DEVELOPERKNOWLEDGE_API_KEY="YOUR_API_KEY"

התחברות לשרת

שרת ה-MCP של Developer Knowledge משתמש בתעבורת נתונים ב-HTTP שניתנת להעברה בסטרימינג בנקודת הקצה הבאה:

https://developerknowledge.googleapis.com/mcp

כדי לבצע אימות, צריך להעביר את מפתח ה-API בכותרת ה-HTTP ‏X-Goog-Api-Key בכל בקשה. אל תעבירו את מפתח ה-API כפרמטר של שאילתה בכתובת URL.

בדוגמה הבאה מתבצע חיבור לשרת ומוצגת רשימה של הכלים הזמינים:

Python

import asyncio
import os

from mcp import Client
from mcp.client.streamable_http import (
    create_mcp_http_client,
    streamable_http_client,
)

MCP_URL = "https://developerknowledge.googleapis.com/mcp"

async def main() -> None:
    headers = {"X-Goog-Api-Key": os.environ["DEVELOPERKNOWLEDGE_API_KEY"]}

    async with create_mcp_http_client(headers=headers) as http_client:
        transport = streamable_http_client(MCP_URL, http_client=http_client)
        async with Client(transport) as client:
            tools = await client.list_tools()
            names = ", ".join(tool.name for tool in tools.tools)
            print("Available tools:", names)

if __name__ == "__main__":
    asyncio.run(main())

TypeScript

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  StreamableHTTPClientTransport,
} from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const apiKey = process.env.DEVELOPERKNOWLEDGE_API_KEY;
if (!apiKey) {
  throw new Error('Set the DEVELOPERKNOWLEDGE_API_KEY variable.');
}

const transport = new StreamableHTTPClientTransport(
  new URL('https://developerknowledge.googleapis.com/mcp'),
  { requestInit: { headers: { 'X-Goog-Api-Key': apiKey } } },
);

const client = new Client({ name: 'my-docs-agent', version: '1.0.0' });
await client.connect(transport);

const { tools } = await client.listTools();
console.log('Available tools:', tools.map((tool) => tool.name).join(', '));

await client.close();

אם החיבור מצליח, הפלט מציג את שלושת הכלים:

Available tools: search_documents, answer_query, get_documents

שאר הדוגמאות במדריך הזה פועלות בתוך הלקוח המחובר מהקטע הזה. בדוגמאות של TypeScript נעשה שימוש ב-await ברמה העליונה, לכן צריך להריץ אותן כמודולים של ES (לדוגמה, באמצעות npx tsx index.ts).

קריאה לכלים

כל כלי מחזיר את התוצאה שלו כתוכן מובנה שתואם לסכימת הפלט של הכלי, כך שאתם יכולים לקרוא שדות ישירות במקום לנתח טקסט. הסכימות המלאות של הקלט והפלט מפורטות בחומר העזר בנושא MCP.

חיפוש מסמכים

קוראים לפונקציה search_documents עם מחרוזת query כדי לאחזר את חלקי התיעוד הרלוונטיים ביותר. כל תוצאה מכילה את החלק content ואת שם המשאב parent של המסמך שממנו הוא הגיע:

Python

search = await client.call_tool(
    "search_documents",
    {"query": "How do I create a Cloud Storage bucket?"},
)
results = search.structured_content["results"]
for chunk in results[:3]:
    print(f"{chunk['parent']}\n{chunk['content'][:200]}...\n")

TypeScript

const search = await client.callTool({
  name: 'search_documents',
  arguments: { query: 'How do I create a Cloud Storage bucket?' },
});
const { results } = search.structuredContent as {
  results: { parent: string; id: string; content: string }[];
};
for (const chunk of results.slice(0, 3)) {
  console.log(`${chunk.parent}\n${chunk.content.slice(0, 200)}...\n`);
}

אחזור מסמכים מלאים

אם קטע לא מכיל מספיק הקשר, מעבירים ערך אחד או יותר של parent אל get_documents כדי לאחזר את תוכן ה-Markdown המלא של כל דף. אפשר לבקש עד 20 מסמכים בקריאה אחת. כמה קטעים יכולים להגיע מאותו מסמך, לכן קודם צריך להסיר ערכים כפולים של parent:

Python

names = list(dict.fromkeys(chunk["parent"] for chunk in results[:3]))
get = await client.call_tool("get_documents", {"names": names})
for document in get.structured_content["documents"]:
    print(f"{document['title']} ({document['uri']})")
    print(document["content"])

TypeScript

const names = [...new Set(results.slice(0, 3).map((c) => c.parent))];
const get = await client.callTool({
  name: 'get_documents',
  arguments: { names },
});
const { documents } = get.structuredContent as {
  documents: {
    name: string;
    uri: string;
    title: string;
    content: string;
  }[];
};
for (const document of documents) {
  console.log(`${document.title} (${document.uri})`);
  console.log(document.content);
}

מסמכים מלאים יכולים להיות גדולים, לכן צריך להתקשר אל get_documents רק אם חלקי החיפוש לא מספיקים. מידע נוסף זמין במאמר בנושא אופטימיזציה של חלון ההקשר והשימוש בטוקנים.

יצירת תשובה

תתקשר אל answer_query כדי ליצור תשובה שמבוססת על מאגר הידע למפתחים. התוצאה מכילה את answerText ואת references ששימשו ליצירתה:

Python

answer = await client.call_tool(
    "answer_query",
    {"query": "How do I create a Cloud Storage bucket?"},
)
print(answer.structured_content["answerText"])
for reference in answer.structured_content["references"]:
    print(reference)

TypeScript

const answer = await client.callTool({
  name: 'answer_query',
  arguments: { query: 'How do I create a Cloud Storage bucket?' },
});
const { answerText, references } = answer.structuredContent as {
  answerText: string;
  references: string[];
};
console.log(answerText);
console.log(references.join('\n'));

השדה references מכיל שמות של משאבי מסמכים שאפשר להעביר אל get_documents. אותו מסמך יכול להופיע יותר מפעם אחת, לכן צריך להסיר כפילויות לפני שמאחזרים את המסמכים. answer_query למכסה של הכלי הזה יש ערך נמוך יותר מאשר לכלים האחרים. מידע נוסף זמין במאמר בנושא מכסות ומגבלות.

אימות באמצעות Application Default Credentials

במקום מפתח API, אפשר לבצע אימות באמצעות Application Default Credentials‏ (ADC). במהלך הפיתוח המקומי, ADC פועל עם פרטי הכניסה של המשתמש, וכשהקוד פועל ב-Google Cloud, הוא פועל עם חשבונות שירות.

כשמשתמשים ב-ADC, צריך לשלוח את הכותרות הבאות בכל בקשה:

  • ‫Authorization: אסימון גישה מסוג OAuth 2.0 של Bearer עם היקף ההרשאות https://www.googleapis.com/auth/cloud-platform.
  • ‫X-Goog-User-Project: מזהה הפרויקט שבו פועל Developer Knowledge API, ושהחיוב על המכסה מתבצע בו. לחשבון שלכם צריכה להיות ההרשאה serviceusage.services.use בפרויקט הזה.

תוקף טוקני הגישה פג, ולכן לקוחות שפועלים לאורך זמן צריכים לרענן אותם. בדוגמאות הבאות נעשה שימוש בספריות האימות של Google כדי להוסיף אסימון נוכחי לכל בקשה.

  1. כדי להגדיר ADC לפיתוח מקומי, פועלים לפי השלבים שבכרטיסייה ADC בקטע בחירת סוג פרטי הכניסה ל-OAuth.

  2. מגדירים את משתנה הסביבה GOOGLE_CLOUD_PROJECT לפרויקט שבו פועל Developer Knowledge API. בדוגמאות האלה, מזהה הפרויקט נשלח בכותרת X-Goog-User-Project:

    export GOOGLE_CLOUD_PROJECT="PROJECT_ID"
    
  3. מתקינים את ספריית האימות של Google בשפה הרצויה:

    Python

    pip install --upgrade google-auth requests
    

    TypeScript

    npm install google-auth-library
    
  4. יוצרים את הלקוח באמצעות ADC:

    Python

    import os
    
    import google.auth
    import google.auth.transport.requests
    import httpx2  # Installed automatically with mcp>=2
    from mcp import Client
    from mcp.client.streamable_http import (
        create_mcp_http_client,
        streamable_http_client,
    )
    
    MCP_URL = "https://developerknowledge.googleapis.com/mcp"
    
    class GoogleCredentialsAuth(httpx2.Auth):
        """Adds a current ADC access token and quota project to requests."""
    
        def __init__(self, project_id: str) -> None:
            self.credentials, _ = google.auth.default(
                scopes=["https://www.googleapis.com/auth/cloud-platform"]
            )
            self.project_id = project_id
    
        def auth_flow(self, request):
            if not self.credentials.valid:
                self.credentials.refresh(
                    google.auth.transport.requests.Request()
                )
            request.headers["Authorization"] = (
                f"Bearer {self.credentials.token}"
            )
            request.headers["X-Goog-User-Project"] = self.project_id
            yield request
    
    async def main() -> None:
        auth = GoogleCredentialsAuth(os.environ["GOOGLE_CLOUD_PROJECT"])
        async with create_mcp_http_client(auth=auth) as http_client:
            transport = streamable_http_client(
                MCP_URL, http_client=http_client
            )
            async with Client(transport) as client:
                ...
    

    TypeScript

    import { GoogleAuth } from 'google-auth-library';
    import { Client } from '@modelcontextprotocol/sdk/client/index.js';
    import {
      StreamableHTTPClientTransport,
    } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
    
    const auth = new GoogleAuth({
      scopes: 'https://www.googleapis.com/auth/cloud-platform',
    });
    const projectId = process.env.GOOGLE_CLOUD_PROJECT;
    if (!projectId) {
      throw new Error('Set the GOOGLE_CLOUD_PROJECT variable.');
    }
    
    const transport = new StreamableHTTPClientTransport(
      new URL('https://developerknowledge.googleapis.com/mcp'),
      {
        // Add a current access token and the quota project to each request.
        fetch: async (url, init) => {
          const headers = new Headers(init?.headers);
          for (const [name, value] of await auth.getRequestHeaders()) {
            headers.set(name, value);
          }
          headers.set('X-Goog-User-Project', projectId);
          return fetch(url, { ...init, headers });
        },
      },
    );
    
    const client = new Client({ name: 'my-docs-agent', version: '1.0.0' });
    await client.connect(transport);
    

טיפול בשגיאות

שגיאות משרת ה-MCP של Developer Knowledge מגיעות לקוד שלכם בשתי דרכים:

  • שגיאות בבקשות: שגיאות שקשורות לאימות, להרשאות ולמכסות מוחזרות עם סטטוס שגיאת HTTP, כמו 400,‏ 403 או 429. ערכות ה-SDK מעלות את השגיאות האלה כחריגות: MCPError ב-Python ו-StreamableHTTPError ב-TypeScript. כדאי לטפל בחריגים האלה בשיחות עם כלים.
  • שגיאות בכלי: כשהכלי פועל אבל לא מצליח להשלים את הבקשה, התוצאה כוללת את הערך true בשדה is_error (Python) או isError (TypeScript), והשדה content מכיל תיאור טקסטואלי של הבעיה. חשוב לבדוק את השדה הזה לפני שקוראים את התוכן המובנה.

הדוגמה הבאה מטפלת בשני סוגי השגיאות:

Python

from mcp import MCPError

try:
    result = await client.call_tool(
        "search_documents", {"query": "Cloud Storage buckets"}
    )
except MCPError as error:
    print(f"Request failed ({error.code}): {error.message}")
else:
    if result.is_error:
        print("Tool error:", result.content)
    else:
        results = result.structured_content["results"]

TypeScript

import {
  StreamableHTTPError,
} from '@modelcontextprotocol/sdk/client/streamableHttp.js';

try {
  const result = await client.callTool({
    name: 'search_documents',
    arguments: { query: 'Cloud Storage buckets' },
  });
  if (result.isError) {
    console.error('Tool error:', result.content);
  } else {
    const { results } = result.structuredContent as {
      results: { parent: string; id: string; content: string }[];
    };
  }
} catch (error) {
  if (error instanceof StreamableHTTPError) {
    console.error(`Request failed (${error.code}): ${error.message}`);
  } else {
    throw error;
  }
}

אם answer_query מחזיר שגיאת מכסה (429 RESOURCE_EXHAUSTED), חוזרים ל-search_documents, שיש לו מכסה גבוהה יותר. במאמר טיפול בשגיאות וניהול מכסות מפורטות אסטרטגיות לניסיון חוזר, כולל השהיה מעריכית לפני ניסיון חוזר (exponential backoff). במאמרים שגיאות במפתח API ושגיאות ב-OAuth וב-ADC מפורטות הסיבות הנפוצות לשגיאות אימות.

המאמרים הבאים