使用 SQL MCP Server 搭配本機模型

Important

SQL 模型情境協定(MCP)伺服器可在資料 API 建構器 1.7 版本中提供。 想獲得最新功能和錯誤修正,請使用 2.0 預覽版。

SQL 模型情境協定(MCP)伺服器可支援任何相容 MCP 用戶端,而非僅限於雲端託管的 AI 服務。 如果您的環境限制了雲端大型語言模型(LLM)存取——這在醫療保健、國防、金融、能源及海事產業中很常見——你可以連接透過 Ollama 或類似工具服務的本地模型。 本指南涵蓋設置、欄位元資料配置,以及讓小型本地模型可靠的提示模式。

先決條件

  • 已安裝並設定 Data API 建構器 CLI,且已設定至少一個實體。 安裝 CLI
  • Ollama 搭配支援工具呼叫的模型(例如, qwen3:8bllama3.1:8b)。
  • Python 3.10+搭配 mcpollama 套件。
  • 一個正在執行的 SQL Server 實例,裡面有資料。

步驟 1:設定欄位元資料

現場元資料是本地模型準確性的最重要的配置步驟。 若沒有欄位名稱與描述,代理人只會看到實體名稱,並錯誤猜測欄位名稱。

警告

跳過此步驟會產生一個在技術上可以運作,但在功能上任何會讀取工具回應的模型都無法實際使用的 MCP 伺服器。 模型沒有您欄位的相關資訊。

先新增實體並加上描述,然後再新增欄位描述,其中包含受限欄位的有效值:

dab add ServerInventory \
  --source dbo.ServerInventory \
  --permissions "anonymous:read" \
  --description "SQL Server instance inventory with version, environment, and sizing data"

dab update ServerInventory \
  --fields.name InstanceName --fields.primary-key true \
  --fields.description "SQL Server instance name (e.g., YOURSERVER01)"

dab update ServerInventory \
  --fields.name Environment \
  --fields.description "Deployment environment. Valid values: Prod, Dev, Test, UAT"

欲了解完整的 CLI 參考與最佳實務——包括受限值、參數描述及腳本模式——請參見 「將描述加入實體」。

Note

dab update CLI 將逗號視為論元分隔符。 如果你的描述包含逗號,請直接編輯 dab-config.json

步驟 2:啟動 SQL MCP 伺服器

dab start

SQL MCP 伺服器預設使用可串流的 HTTP 傳輸,並在 http://localhost:5000/mcp 上接聽。 任何實作 MCP 協定的用戶端都能連接到此端點。

步驟 3:連接你當地的模型

建立一個 MCP 客戶端,將你的 Ollama 模型連接到 SQL MCP Server。 以下Python範例使用 MCP Python SDK 套件。

安裝依賴項

pip install mcp ollama

極簡 Python 線束

import asyncio
import json
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
import ollama

MCP_URL = "http://localhost:5000/mcp"
MODEL = "qwen3:8b"

async def get_schema(session: ClientSession) -> str:
    """Call describe_entities and format results for the system prompt."""
    result = await session.call_tool("describe_entities", arguments={})
    entities = json.loads(result.content[0].text)
    lines = []
    for entity in entities.get("entities", []):
        fields = ", ".join(
            f"{f['name']} ({f.get('description', 'no description')})"
            for f in entity.get("fields", [])
        )
        lines.append(f"- {entity['name']}: {entity.get('description', '')}")
        if fields:
            lines.append(f"  Fields: {fields}")
    return "\n".join(lines)

async def run(user_question: str):
    async with streamable_http_client(MCP_URL) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Preinject schema into the system prompt
            schema_text = await get_schema(session)
            system_prompt = f"""You query a SQL database through MCP tools.

Available entities:
{schema_text}

Rules:
- Use the exact field names shown above.
- Answer count questions with the count only.
- Do not produce summaries unless asked.
- Do not invent example data. Only return data from tool responses.
- If no results, say "No results found" and stop.
"""
            # Get available tools for Ollama
            tools_result = await session.list_tools()
            ollama_tools = [
                {
                    "type": "function",
                    "function": {
                        "name": t.name,
                        "description": t.description or "",
                        "parameters": t.inputSchema,
                    },
                }
                for t in tools_result.tools
            ]

            messages = [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_question},
            ]

            # Chat loop: let the model call tools until it produces a final answer
            while True:
                response = ollama.chat(
                    model=MODEL, messages=messages, tools=ollama_tools
                )
                msg = response["message"]
                messages.append(msg)

                if not msg.get("tool_calls"):
                    print(msg["content"])
                    break

                for tc in msg["tool_calls"]:
                    result = await session.call_tool(
                        tc["function"]["name"],
                        arguments=tc["function"]["arguments"],
                    )
                    messages.append(
                        {
                            "role": "tool",
                            "content": result.content[0].text,
                        }
                    )

asyncio.run(run("How many SQL 2019 servers are in production?"))

此工具涵蓋完整流程:模式預注入、工具發現、多回合工具呼叫,以及最終回應擷取。 調整 MODELMCP_URL 適應你的環境。

啟動時的預注入結構

小型本地模型(參數少於 14B)在對話開始前,若系統提示中包含結構描述中繼資料,會產生更可靠的工具呼叫。 與其在對話中依賴模型自行呼叫 describe_entities ,不如在 harness startup 時呼叫並注入結果。

為什麼注射前很重要

方法 使用小型模型時的行為
動態探索 模型必須先決定呼叫 describe_entities ,再解讀結果,再呼叫具有正確欄位名稱的正確工具。 多個故障點。
預注射 模型能立即看到實體名稱、欄位名稱和描述。 第一次嘗試就正確地選對了工具。

前一節的背帶範例展示了這種模式。 get_schema() 函式會在啟動時呼叫一次 describe_entities,並將結果格式化後放入系統提示。

Tip

較大型的雲端模型(如 GPT-4o、Claude)通常無須預先注入資訊,就能在對話過程中推斷出結構描述(schema)。 此模式對於參數為14B的模型最有價值。

限制模型回應

模型可以做出正確的工具呼叫,取得正確的資料,但仍會產生錯誤的答案。 例如,當被問到「有多少台生產伺服器?」時,某個模型可能會正確擷取到 16 列資料,接著卻回覆一份長達 40 行、包含憑空捏造範例的主管摘要,而不是數字 16

在系統提示中加入明確的負面規則:

Rules:
- Answer count questions with the count only.
- Do not produce summaries unless the user asks for one.
- Do not invent example data. Only return data from tool responses.
- If a tool returns no results, say "No results found" and stop.

工具呼叫的準確度與接答紀律是不同的問題。 DAB 透過工具層確保能準確擷取資料。 你的提示束帶控制模型呈現結果的方式。

Considerations

主題 詳細資訊
硬體 工具呼叫功能可在規格不高的硬體上運作。 在消費級 Nvidia GPU(8 GB 視訊記憶體)上採用 8B 參數模型,能產生有用的結果。 每個問題的延遲時間預計為數十秒,因此適合批次工作負載。
批次與互動式 小型模型非常適合批次處理(效能報告、庫存查詢),因為延遲容忍度較高。
工具可用性 aggregate_records 僅在 2.0 版本及之後版本預覽版中提供。 在 1.7.x 版本中,計數與彙總查詢會強制模型讀取所有相符的列。 請參閱 依版本分類的工具可用性
交通 本地模型透過可串流的 HTTP 連接到 /mcp標準輸入/輸出(stdio)傳輸是單程序設置的替代方案。
驗證 在地開發時,請使用 anonymous 權限。 對於生產環境,請設定符合你環境的 認證