DEV Community

Ali Orhun Akkirman for Açıklab

Posted on

Python ile basit bir MCP Sunucu-İstemci örneği

LLM'lerin araçlarla etkileşimi konusunda önemli bir adım olan MCP için python üzerinde en temelde FastMCP aracı kullanılmakta. Tabi ki bu araçlar sürekli güncellenip gelişmekte fakat yine de bu tarih için en geniş kullanıma sahip kütüphanesi olarak düşünülebilir.

MCP Sunucu Kurulum

Öncelikle mimarideki MCP sunucu kurulumu için aşağıdaki komutla fastmcp modülünün kurulması gerekmektedir.

pip instal fastmcp
Enter fullscreen mode Exit fullscreen mode

Bu komutla birlikte sistemde Python üzerinde fastmcp modülü kullanılabilir duruma gelinmekte.

MCP Sunucu Yapılandırma

Bu adımdan sonra bir de basit bir sunucu uygulaması hazırlayalım. Uygulamanın test açısından son hali aşağıdaki gibi düşünebiliriz. Buradaki örnekte basit bir mcp fonksiyon aracı ile başka bir REST API'yi çağıran farklı bir fonksiyon aracının da bulunduğunu göreceğiz.

from fastmcp import FastMCP
import requests
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from mcp.server.sse import SseServerTransport

mcp = FastMCP("Test MCP Sunucusu")

@mcp.tool()
def hello(name: str) -> str:
    return f"Merhaba {name}!"


@mcp.tool()
def exTool1(user: str) -> str:
    return f"Kullanıcı {user} !"

transport = SseServerTransport("/messages/")

async def handle_sse(request):
    async with transport.connect_sse(
        request.scope,
        request.receive,
        request._send
    ) as (in_stream, out_stream):
        await mcp._mcp_server.run(
            in_stream,
            out_stream,
            mcp._mcp_server.create_initialization_options()
        )

@mcp.tool()
def getirHavaDurumu(sehir: str) -> dict:
    url = f"https://api.open-meteo.com/v1/forecast?latitude=41.01&longitude=28.97&current_weather=true"
    response = requests.get(url)
    data = response.json()
    return {"sehir": sehir, "sonuc": data["current_weather"]}

#Build a small Starlette app for the two MCP endpoints
sse_app = Starlette(
    routes=[
        Route("/sse", handle_sse, methods=["GET"]),
        # Note the trailing slash to avoid 307 redirects
        Mount("/messages/", app=transport.handle_post_message)
    ]
)


if __name__ == "__main__":
    mcp.run(transport="http")
Enter fullscreen mode Exit fullscreen mode

Bu uygulamadaki en önemli parça @mcp.tool() ile başlayan kısımlar. Bu kısımlar istemci (client) üzerinde kullanılabilir araç olarak görülebilecektir. Örneğin yukarıdaki uygulamada hello, exTool1 ve getirHavaDurumu şeklinde 3 tane araç bulunmakta.

Her araç için fonksiyon içerisindeki parametreler aldığını görmekteyiz. Örneğin hello'da name, exTool1'de user ve getirHavaDurumu'nda ise sehir olacak şekilde. Bu parametrelerin kullanımı fonksiyon içerisinde net bir şekilde anlaşıldığını düşünüyorum. Bir örnek açısından yukarıdaki uygulamada sehir parametresi requests modülü ile open-meteo API'sine sorgu atararak sıcaklık ve rüzgar değerlerini çekebilmekte.

Diğer kısımlara şuan için girmiyorum.

Bu adımdan sonra hangi isimle dosyayı kaydederseniz bu şekilde çağırmanız yeterli.

python3 server.py 
Enter fullscreen mode Exit fullscreen mode

Bu komutun çıktısı aşağıdaki gibi olması beklenmekte.

╭────────────────────────────────────────────────────────────────────────────╮
│                                                                            │
│        _ __ ___  _____           __  __  _____________    ____    ____     │
│       _ __ ___ .'____/___ ______/ /_/  |/  / ____/ __ \  |___ \  / __ \    │
│      _ __ ___ / /_  / __ `/ ___/ __/ /|_/ / /   / /_/ /  ___/ / / / / /    │
│     _ __ ___ / __/ / /_/ (__  ) /_/ /  / / /___/ ____/  /  __/_/ /_/ /     │
│    _ __ ___ /_/    \____/____/\__/_/  /_/\____/_/      /_____(*)____/      │
│                                                                            
│                                FastMCP  2.0                                │
│               🖥️  Server name:     Test MCP Sunucusu                          │
│               📦 Transport:       Streamable-HTTP                          │
│               🔗 Server URL:      http://127.0.0.1:8000/mcp                │
│               🏎️  FastMCP version: 2.12.0                                   │
│               🤝 MCP SDK version: 1.13.1                                   │
│               📚 Docs:            https://gofastmcp.com                    │
│               🚀 Deploy:          https://fastmcp.cloud                    │
╰────────────────────────────────────────────────────────────────────────────╯


[09/02/25 23:45:24] INFO     Starting MCP server 'Test MCP Server'    server.py:1571
                             with transport 'http' on                               
                             http://127.0.0.1:8000/mcp                              
INFO:     Started server process [281559]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Enter fullscreen mode Exit fullscreen mode

Bu çıktı ile MCP sunucumuzun 8000 portu üzerinden ayağa kalktığını söyleyebiliriz.

Sıra geldi MCP İstemciye

İstemci yapılandırılması için ise aşağıdaki uygulamayı kullanacağız.

Öncelikle kurulum için aşağıdaki komutu kullanabiliriz:

pip install "mcp[cli]"
Enter fullscreen mode Exit fullscreen mode

Sonrasında sunucuyu kullanacak olan örnek bir istemci uygulaması aşağıdaki gibi kurgulanabilir.

import ast
import asyncio
import pprint

from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

# --- Configuration ---
SERVER_URL = "http://localhost:8000/mcp"
pp = pprint.PrettyPrinter(indent=2, width=100)


def unwrap_tool_result(resp):
    """
    Safely unwraps the content from a FastMCP tool call result object.
    """
    if hasattr(resp, "content") and resp.content:
        # The content is a list containing a single content object
        content_object = resp.content[0]
        # It could be JSON or plain text
        if hasattr(content_object, "json"):
            return content_object.json
        if hasattr(content_object, "text"):
            try:
                # Use ast.literal_eval for safely evaluating a string containing a Python literal
                return ast.literal_eval(content_object.text)
            except (ValueError, SyntaxError):
                # If it's not a literal, return the raw text
                return content_object.text
    return resp


async def main():
    transport = StreamableHttpTransport(url=SERVER_URL)
    client = Client(transport)
    print("\nConnecting to FastMCP server at:", SERVER_URL)
    async with client:
        # 1. Ping to test connectivity
        print("\nTesting server connectivity...")
        await client.ping()
        print("✅ Server is reachable!\n")

        # 2. Discover server capabilities
        print("🛠️   Available tools:")
        pp.pprint(await client.list_tools())
        print("\n📚 Available resources:")
        pp.pprint(await client.list_resources())
        print("\n💬 Available prompts:")
        pp.pprint(await client.list_prompts())

       # 3. Test tool
        print("\n\n🔍 Testing tool: havadurumu")
        raw_search = await client.call_tool(
            "getirHavaDurumu",
            {"sehir": "zonguldak",},
        )
        search_results = unwrap_tool_result(raw_search)
        pp.pprint(search_results)

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Bu uygulamadaki en önemli kısım ise yapılandırma kısmındaki SERVER_URL olarak sunucunun adres ve portunun yazılması gerekliliği olacak.

Sonrasında FastMCP araç çağrısını okunabilir noktaya getirecek unwrap_tool_result fonksiyonunu görüyoruz.

Ama asıl önemli kısım main fonksiyonunda yer alan kısımlar.

İlk adımda sunucuya sağlıklı erişip erişmediğinin kontrolü, sonrasında da sunucuda yer alan araç, kaynak ve prompt değerlerini göstermekte. Şuan için sadece araç kullandığımız için diğerleri boş gelecektir.

Araçlar içerisinde de hava durumunu sorguladığımız ve örnekte "zonguldak" ili için hava durumunu sorguladığımızı gösterecek.

Dolayısıyla aşağıdaki komut ile istemci uygulaması çalıştırılabilir.

python3 client.py 
Enter fullscreen mode Exit fullscreen mode

Bu komutun çıktısı da aşağıdaki gibi olacağı öngörülmekte.

Connecting to FastMCP server at: http://localhost:8000/mcp

Testing server connectivity...
✅ Server is reachable!

🛠️  Available tools:
[ Tool(name='hello', title=None, description=None, inputSchema={'properties': {'name': {'title': 'Name', 'type': 'string'}}, 'required': ['name'], 'type': 'object'}, outputSchema={'properties': {'result': {'title': 'Result', 'type': 'string'}}, 'required': ['result'], 'title': '_WrappedResult', 'type': 'object', 'x-fastmcp-wrap-result': True}, annotations=None, meta={'_fastmcp': {'tags': []}}),
  Tool(name='exTool1', title=None, description=None, inputSchema={'properties': {'user': {'title': 'User', 'type': 'string'}}, 'required': ['user'], 'type': 'object'}, outputSchema={'properties': {'result': {'title': 'Result', 'type': 'string'}}, 'required': ['result'], 'title': '_WrappedResult', 'type': 'object', 'x-fastmcp-wrap-result': True}, annotations=None, meta={'_fastmcp': {'tags': []}}),
  Tool(name='getirHavaDurumu', title=None, description=None, inputSchema={'properties': {'sehir': {'title': 'Sehir', 'type': 'string'}}, 'required': ['sehir'], 'type': 'object'}, outputSchema={'additionalProperties': True, 'type': 'object'}, annotations=None, meta={'_fastmcp': {'tags': []}})]

📚 Available resources:
[]

💬 Available prompts:
[]


🔍 Testing tool: havadurumu
<bound method BaseModel.json of TextContent(type='text', text='{"sehir":"zonguldak","sonuc":{"time":"2025-09-02T20:45","interval":900,"temperature":22.8,"windspeed":3.0,"winddirection":14,"is_day":0,"weathercode":1}}', annotations=None, meta=None)>

Enter fullscreen mode Exit fullscreen mode

Tüm bu adımlarla birlikte aslında basitçe bir MCP sunucu-istemci uygulamasını ayağa kaldırıp kullanmış oluyoruz.

Tabi ki bu yazıda MCP uygulamasının detaylarına, LLM'lerle ilişkisine veya farklı detaylı konulara girmeden basitçe kullanımını derli toplu hazırlamak istedim. Umarım yeni başlayanlar için faydalı bir yazı olur.

Kaynak olarak kullandığım adres.

~~~~~~~~~~~~~~~~~~

"Her yeni başlangıç, başka bir başlangıcın sonundan gelir." Seneca

Top comments (0)