पाठ 6 / 31

Initialize Handshake और Capabilities

देखें client और server protocol संस्करण और सुविधाओं पर कैसे सहमत होते हैं।

नमस्ते कहें, सहमत हों, फिर काम करें

हर MCP session client के initialize request से शुरू होता है जिसमें उसका protocolVersion, capabilities (जैसे sampling या roots का समर्थन) और clientInfo होते हैं। Server उस संस्करण से उत्तर देता है जो वह उपयोग करेगा, अपनी capabilities (tools, resources, prompts, listChanged जैसे flags सहित) और serverInfo के साथ। फिर client notifications/initialized notification भेजता है और सामान्य traffic शुरू होता है। हर पक्ष सिर्फ़ वही सुविधाएँ उपयोग कर सकता है जो दोनों ने घोषित कीं; यह capability negotiation है, और नई सुविधाएँ आने पर protocols इसी तरह backward compatible रहते हैं। बाद में transport बंद करने पर session समाप्त होता है।

बात करने के लिए एक server

यह छोटा server (आधिकारिक Python SDK, संस्करण 2.x से बना) एक tool, एक resource और एक prompt उपलब्ध कराता है। इसे server.py नाम से सहेजें। अगले उदाहरण इसे कच्चे JSON-RPC से चलाते हैं।

from mcp.server.mcpserver import MCPServer

mcp = MCPServer("demo-orders")

@mcp.tool()
def get_order_status(order_id: str) -> dict:
    """Return the status of ONE order. order_id is exactly 6 digits."""
    if not (order_id.isdigit() and len(order_id) == 6):
        return {"error": "order_id must be exactly 6 digits"}
    return {"order_id": order_id, "status": "shipped"}

@mcp.resource("policy://refunds")
def refund_policy() -> str:
    """The refund policy text."""
    return "Refunds above 5000 rupees need approval."

@mcp.prompt()
def triage(ticket: str) -> str:
    """A prompt template for ticket triage."""
    return f"Classify this ticket as billing, technical or other: {ticket}"

if __name__ == "__main__":
    mcp.run("stdio")

Server को हाथ से चलाना, चलाकर

मैंने यह पहले दिखाए असली server के विरुद्ध चलाया, virtual environment में आधिकारिक mcp Python SDK 2.2.0 के साथ; client पक्ष stdio पर कच्चा JSON-RPC है, SDK के बिना। Server को उसी folder में server.py नाम से सहेजें। SDK के नाम और डिफ़ॉल्ट संस्करणों के बीच बदलते हैं, इसलिए मौजूदा दस्तावेज़ देखें। Client initialize, फिर initialized notification भेजता है, फिर tools सूचीबद्ध करता है, वैध, अमान्य और ग़ायब arguments के साथ एक को बुलाता है, अज्ञात method आज़माता है, और एक resource व एक prompt पढ़ता है। Server protocol 2025-06-18 और capabilities prompts, resources, tools (साथ में experimental प्रविष्टि) रिपोर्ट करता है।

import json, subprocess, sys

proc = subprocess.Popen([sys.executable, "server.py"], stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)

def send(msg):
    proc.stdin.write(json.dumps(msg) + "\n"); proc.stdin.flush()

def call(id_, method, params=None):
    send({"jsonrpc": "2.0", "id": id_, "method": method, **({"params": params} if params is not None else {})})
    return json.loads(proc.stdout.readline())

init = call(1, "initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                              "clientInfo": {"name": "demo-client", "version": "0.1"}})
print("server:", init["result"]["serverInfo"]["name"], "| protocol:", init["result"]["protocolVersion"])
print("capabilities:", sorted(init["result"]["capabilities"]))
send({"jsonrpc": "2.0", "method": "notifications/initialized"})     # a notification: no id, no reply

tools = call(2, "tools/list")["result"]["tools"]
print("tools:", [t["name"] for t in tools])
print("input schema:", json.dumps(tools[0]["inputSchema"]))

ok = call(3, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "481516"}})
print("call ok     :", ok["result"]["content"][0]["text"], "| isError:", ok["result"].get("isError"))
bad = call(4, "tools/call", {"name": "get_order_status", "arguments": {"order_id": "12"}})
print("call bad id :", bad["result"]["content"][0]["text"])
missing = call(5, "tools/call", {"name": "get_order_status", "arguments": {}})
print("missing arg :", "isError" , missing["result"].get("isError"), "|", missing["result"]["content"][0]["text"][:60].replace(chr(10), " "))
unknown = call(6, "no/such/method")
print("unknown     :", unknown["error"]["code"], unknown["error"]["message"])

res = call(7, "resources/list")["result"]["resources"]
print("resources:", [r["uri"] for r in res])
print("read:", call(8, "resources/read", {"uri": "policy://refunds"})["result"]["contents"][0]["text"])
pr = call(9, "prompts/get", {"name": "triage", "arguments": {"ticket": "I was charged twice"}})
print("prompt:", pr["result"]["messages"][0]["content"]["text"])
proc.stdin.close(); proc.wait()

Output:

server: demo-orders | protocol: 2025-06-18
capabilities: ['experimental', 'prompts', 'resources', 'tools']
tools: ['get_order_status']
input schema: {"properties": {"order_id": {"title": "Order Id", "type": "string"}}, "required": ["order_id"], "type": "object", "title": "get_order_statusArguments"}
call ok     : {
  "order_id": "481516",
  "status": "shipped"
} | isError: False
call bad id : {
  "error": "order_id must be exactly 6 digits"
}
missing arg : isError True | Error executing tool get_order_status: 1 validation error fo
unknown     : -32601 Method not found
resources: ['policy://refunds']
read: Refunds above 5000 rupees need approval.
prompt: Classify this ticket as billing, technical or other: I was charged twice

Capability negotiation, चलाकर

मैंने यह सादे Python 3 (सिर्फ़ standard library) से चलाया। सिर्फ़ वही सुविधाएँ उपयोग योग्य हैं जो दोनों पक्षों ने घोषित कीं: client capabilities के बिना सिर्फ़ tools और resources चलते हैं; sampling के लिए client का घोषित करना और server का चाहना ज़रूरी है; जिस client ने roots घोषित नहीं किया उससे माँगना अनदेखा होता है।

def negotiate(client_caps, server_caps):
    """Use only features that BOTH sides declared during initialize."""
    usable = []
    if "tools" in server_caps:
        usable.append("tools")
    if "resources" in server_caps:
        usable.append("resources")
    if "sampling" in client_caps and server_caps.get("wants_sampling"):
        usable.append("sampling (server asks the client's model)")
    if "roots" in client_caps and server_caps.get("wants_roots"):
        usable.append("roots")
    return usable

print(negotiate({}, {"tools": {}, "resources": {}}))
print(negotiate({"sampling": {}}, {"tools": {}, "wants_sampling": True}))
print(negotiate({"sampling": {}}, {"tools": {}, "wants_roots": True}))

Output:

['tools', 'resources']
['tools', "sampling (server asks the client's model)"]
['tools']

त्वरित जाँच: Server के initialize का उत्तर देने के ठीक बाद क्या होता है?

  • फिर कुछ नहीं हो सकता
  • Session हटा दिया जाता है
  • मॉडल दोबारा प्रशिक्षित होता है
  • Client notifications/initialized भेजता है और सामान्य traffic शुरू होता है
Answer

Client notifications/initialized भेजता है और सामान्य traffic शुरू होता है — initialized notification handshake पूरा करता है।