FastMCP Голасовы стак Part 1: Адзінсткі, Stdio/HTTP, STT і TTS
Частка 1 голасовага агента: сервер адзінства MCP з FastMCP праз stdio і HTTP, дапаможнікі для паводка і візуальнага аналізу, а таксама падключэнне мікрафона для перакладу голасу ў тэкст і выходу голасовага тэксту.
Крыніцы: **Part 1: VOICE ASSISTANT, FASTMCP, LANGRAPH(J.A.V.I.S)🤖**
Стварэнне агента на базе голасу з адпаведнымі інструментамі
У гэтым кераванні рассказваецца пра серверы MCP, пераклад голасу ў тэкст, пераклад тэксту ў голас, а таксама пра парадокс, у яком агент чуе голас, выкарыстоўвае інструменты праз MCP і выдае рэзультаты у голасовай форме. «LLM» — гэта большая мовная мадэль, якая выконвае рашэнні.
FastMCP і протакол Model Context
MCP — гэта адкрыты протакол, які даступнаець мадэлям інструменты, рэсурсы і запіты. Сервер MCP выклікае інструменты; кліенты (агенты LangChain, Claude Desktop, Cursor і падобныя) паў’язуюцца з серверам і дазволяюць модэлі выканаць гэтыя інструменты.
FastMCP — гэта фрэймворк на Python для сервераў MCP; ён аналогічны Flask для веб-дзеяноў або FastAPI для HTTP-API. Чырвоніце звычныя функцыі за дапамою @mcp.tool, ўбачыць, каб іх можна было выклікаць.
Способы перэдачы дадзеных
Stdio. Кліент запускае server.py як падзейны процэс і перадае даныя у фармаце JSON праз stdin/stdout. Не выкарыстоўваецца сетка. Ідэальна для інструментаў, якія працуюць толькі на локальным роўні і залежаць ад процэсу агента.
HTTP. Сервер прыёмляе запыткі на певны хост і порт (напрыклад, 127.0.0.1:8000) і працуе незалежна. Кліенты, якія знаходзяцца на локальным чыста або за далёкай сеткай і можу даць доступ да URL, могу вызваць інструменты. Для выклікання сервера з ноутбука часта выкарыстоўваецца тунель на кшталт Ngrok.
Мінімальны сервер і кліент на стандартных ввод-выход
Сконфігуруйце срэду за дапамою uv, а пасля створыце мінімальны сервер на стандартных ввод-выход:
On windows(Powershell):
PS>> irm https://astral.sh/uv/install.ps1 | iex
Linux:
linux> curl -LsSf https://astral.sh/uv/install.sh | sh
Verify Installation:
Ps>> uv --version
Linux:
linux> uv --version
Create a virtual environment:
Ps>> uv venv
Linux:
linux> uv venv
Now activate our virtual environment:
Ps>> “.venv/Scripts/activate”
Linux:
linux> source .venv/bin/activate
To install packages using uv we do:
Ps>> uv pip install <package_name>
- In our case we need fastMCP library:
Ps>> uv pip install fastmcp
#!/usr/bin/python3
from fastmcp import FastMCP # our library
from datetime import datetime # used in our second tool
# create an object from the library
mcp=FastMCP("Demo🚀🚀")
# Python decorator--> from the FastMCP library
# The "@" decorator below exposes the function as a tool
@mcp.tool
def calculate(a:int, b:int)-> int:
"""
Add two Numbers.
"""
# the above is a Python DocString--> AI reads it to know when to use the tool
return a+b
# lets add another tool
@mcp.tool
def current_time()-> str:
"""
Returns the current local date and time
"""
# call the datetime library to get this
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if __name__=="__main__":
# By default the mode of transport here is "stdio"
mcp.run()
Ключовы моменты: @mcp.tool рэгіструе функцыю; докстрынгі паведамляюць модэлю, калі яе вызваць; mcp.run() за замовчаннем выкарыстоўвае стандартные ввод-выход.
Кліент без LLM можа спісаваць і вызваць інструменты безпосерэдна:
#!/usr/bin/python3
import asyncio
from fastmcp import Client
# our function will be an asynchronous function:
# MCP transport is I/O bound and the async library lets our program
# wait for tool results without freezing the whole program
# Async library also allows the AI to run multiple tools concurrently
async def main():
# we get the server name we have for our server and pass it below
async with Client("mcp_server.py") as client:
# Now list available tools
tools=await client.list_tools()
print(f"Tools: {[t.name for t in tools]}")
# call the add tool
result=await client.call_tool("add", {"a":3, "b":4})
print(f"\nTool Result: {result}")
# call the second tool(remember, it had no arguments)
result=await client.call_tool("current_time")
print(f"\nCurrent time result: {result}")
# run our function
asyncio.run(main())
"""
========Exclude this: Its my tool result when I run on cmd/terminal======
example_2>python mcp_client.py
Tools: ['add', 'current_time']
Tool Result: CallToolResult(content=[TextContent(type='text', text='7', annotations=None, meta=None)], structured_content={'result': 7}, meta={'fastmcp': {'wrap_result': True}}, data=7, is_error=False)
Current time result: CallToolResult(content=[TextContent(type='text', text='2026-08-23 19:31:48', annotations=None, meta=None)], structured_content={'result': '2026-08-23 19:31:48'}, meta={'fastmcp': {'wrap_result': True}}, data='2026-08-23 19:31:48', is_error=False)
"""
Варыант HTTP
Той самы ўстатку, але разны рэжым работы:
#!/usr/bin/python3
from fastmcp import FastMCP # our library
from datetime import datetime # used in our second tool
# create an object from the library
mcp=FastMCP("Demo🚀🚀")
# Python decorator--> from the FastMCP library
# The "@" decorator below exposes the function as a tool
@mcp.tool
def add(a:int, b:int)-> int:
"""
Add two Numbers.
"""
# the above is a Python DocString--> AI reads it to know when to use the tool
return a+b
# lets add another tool
@mcp.tool
def current_time()-> str:
"""
Returns the current local date and time
"""
# call the datetime library to get this
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if __name__=="__main__":
# We now change the mode of transport to http from the default stdio
mcp.run(transport="http", host="127.0.0.1", port=8000)
# We now change the mode of transport to http from the default stdio
mcp.run(transport="http", host="127.0.0.1", port=8000)
Сервер адавяе ўтварэнне на кшталт http://127.0.0.1:8000/mcp. Кліент нацэляеся на гэты URL:
#!/usr/bin/python3
import asyncio
from fastmcp import Client
# our function will be an asynchronous function:
# MCP transport is I/O bound and the async library lets our program
# wait for tool results without freezing the whole program
# Async library also allows the AI to run multiple tools concurrently
async def main():
# we pass the localIP:port url that our server is listening on
async with Client("http://127.0.0.1:8000/mcp") as client:
# Now list available tools
tools=await client.list_tools()
# iterate through the list of tools and display only there names
print(f"Tools: {[t.name for t in tools]}")
# call the add tool
result=await client.call_tool("add", {"a":3, "b":4})
print(f"\nTool Result: {result}")
# call the second tool(remember, it had no arguments)
result=await client.call_tool("current_time")
print(f"\nCurrent time result: {result}")
# run our function
asyncio.run(main())
"""
======Exclude this: Its my tool result when I run on cmd/terminal=======
example_2>python mcp_client.py
Tools: ['add', 'current_time']
Tool Result: CallToolResult(content=[TextContent(type='text', text='7', annotations=None, meta=None)], structured_content={'result': 7}, meta={'fastmcp': {'wrap_result': True}}, data=7, is_error=False)
Current time result: CallToolResult(content=[TextContent(type='text', text='2026-08-23 19:54:50', annotations=None, meta=None)], structured_content={'result': '2026-08-23 19:54:50'}, meta={'fastmcp': {'wrap_result': True}}, data='2026-08-23 19:54:50', is_error=False)
"""
# we pass the localIP:port url that our server is listening on
async with Client("http://127.0.0.1:8000/mcp") as client:
Рэзультаты збігаюцца з stdio; зменяецца толькі топалогія обрабоцкі.
Чаму FastMCP дапамагае агентам
Мадэль бачыць назву, апісанне, параметры і фармат рэзультата — не внутрашні код. Сістэма камунікацыі застаецца ў рамках фрэймворку. Той самы сервер можа знаходзіцца на аддаленай машыне і служыць для больш заўсёды кліентоў, на вядомаць ад інструментаў, якія ўвесь час працуюць толькі внутры адной процэса LangChain.
Сервер 프로екту MCP для голасовага асістэнта
Інструменты, планаваныя для асістэнта: калькулятор, інформацыя пра пагоду, веб-пошук у Tavily, збір даных з Firecrawl у фармаце markdown/JSON, аналіз звярткаў на картынкі, а таксама інформацыя пра дату/час. Для локальной работы вікорыстоўваецца stdio.
Створыце праект з .env для хранення ключоў і .gitignore, які выключае .env з контролю версій. Застосавайце uv для інсталяцыі:
cmd>> uv pip install requests fastmcp simpleeval python-dotenv tavily-python firecrawl-py langchain_groq langchain_core langchain
Размясціце болей длугія падпрамыслы ў пакеце tools:
cmd>> mkdir tools
Падпрамыл для працы з паветарам
Атрымайце ключ OpenWeather, зберыце яго ў .env і рэалізуйце функцыю пошуку інформацыі:
#!/usr/bin/python3
import requests
import os
from dotenv import load_dotenv
# Load our API key from environment variables
load_dotenv()
# access it using the os library
API_KEY=os.getenv("WEB_API")
def get_weather(city):
# fetching weather from open weather web app
url=f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric"
# Send our HTTP request
response=requests.get(url)
# filter and iterate through the request
if response.status_code==200:
# If we connected to the web seaver. Convert the response received to json
data=response.json()
temperature=data["main"]["temp"]
humidity=data["main"]["humidity"]
description=data["weather"][0]["description"]
wind_speed=data["wind"]["speed"]
weather_report = f"""
Current Weather Report
Location: {city}
Temperature: {temperature}°C
Humidity: {humidity}%
Conditions: {description}
Wind Speed: {wind_speed} m/s
"""
# return the above report
return weather_report
else:
return f"Error retrieving weather data: {response.status_code}"
Падпрамыл для працы з візуяльнымі дадзеннямі
Кодуйце зображэннія у фармаце base64 і адправьце іх праз LangChain да модэлю Groq, які падтрымлея візуяльную обработку (напрыклад, SKU Qwen vision/reasoning). Ключ Groq таксама зберыце ў .env.
#!/usr/bin/python3
from langchain_groq import ChatGroq
from dotenv import load_dotenv
import os
import base64
from langchain_core.messages import HumanMessage
# load environment variables--> API keys from the ".env" file
load_dotenv()
# the llm setup---> take note of the model_name that has vision
api_key=os.getenv("GROQ_API_KEY")
llm=ChatGroq(
temperature=0.3,
groq_api_key=os.getenv("GROQ_API_KEY"),
model_name="qwen/qwen3.6-27b"
)
# first step is to convert the image to base64
# We will send it to the API in this format
def encode_image(image):
# open the image in binary and encode it
with open(image, "rb") as f:
return base64.b64encode(f.read()).decode()
# Call the function to send encoded image data to LLM
def describe_image(image):
# get encoded image data
encoded_image=encode_image(image)
# Prepare prompt
message= HumanMessage(
content=[
{
"type":"text",
"text":"Describe this Image. Be brief"
},
{
"type":"image_url",
# fetching image locally. If not local use: 'url':'https:<url_for_iamge>'
"image_url":{
"url":f"data:image/png;base64,{encoded_image}"
}
}
]
)
# call our LLM passing the above prompt with our encoded image data
result=llm.invoke([message])
# get the content part. Thats what we care about to be returned
result=result.content
# Remove reasoning section--> we want to reduce the amount of text returned--> reducing token consumption
if "</think>" in result:
result = result.split("</think>", 1)[1].strip()
return result
# --------Delete this part below after testing and save only the function above-----------
print("[+] Starting description......")
# Make sure image is inthe same directory as vision.py . If not use the filepath
response=describe_image("my_quiz.png")
print(f"\nImage Data: {response}")
"""
=================This is the result I got=======================
cmd>> python vision.py
[+] Starting description......
Image Data: The image displays a text-based "Python Quick Quiz" containing three questions. The text is shown twice: once at the top and again in a zoomed-in view below. The questions are:
1. Define Python in one line
2. Difference between variable and constant in one line
3. Write code to get input from the user and add the two numbers
"""
cmd>> cd ..
# make a file mcp_server.py if using vs code
# Now the shell should look like below
cmd>> ls
tools mcp_server.py .env
Складанне файла mcp_server.py
Дадаце ключы Firecrawl і Tavily, імпортуйце падпрамыслы і зарэгіструйце заставшыяся інструменты:
#!/usr/bin/python3
from datetime import datetime
from fastmcp import FastMCP
from simpleeval import simple_eval
import os
from dotenv import load_dotenv
from tavily import TavilyClient
from firecrawl import FirecrawlApp
from typing import Literal
# user defined function here. Located in the tools directory
from tools.vision import describe_image
from tools.weather import get_weather
# ----------Loads APIs Accessible to our Program----------
#loads environment variables. Our APIs in .env file to be accessible here
load_dotenv()
# we get the Tavily API here--> use os library and save to a variable
tavily_client=TavilyClient(api_key=os.getenv("TAVILLY_API")) # Tavily
# firecrawl setup. Get API key using OS library and save to a variable
app=FirecrawlApp(api_key=os.getenv("FIRECRAWL"))
#---------------------------------------------------------
# create an MCP object from the class
mcp=FastMCP("local-tools")
#-----1. Time tool
# the decorator turns our function into a tool
@mcp.tool
def current_time()-> str:
"""
Return the current local date and time
Use this when the user asks what time or date it is
"""
# Docstring provides tool description to the LLM
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#----2: Calculator to solve expressions
#Next tool will be a calculator. Can solve complex maths equations
@mcp.tool
def calculate(expression: str)-> str:
"""
Safely evaluate a mathematical expression
Returns a string suitable for an LLM
"""
try:
# pass the expression from an LLM here
result=simple_eval(expression)
return f"""
Calculation
Expression: {expression}
Result: {result}
"""
except Exception as e:
return f"Calculation Error: {e}"
#----3. Vision into images
# ability to describe images. We wrote the code in the tools directory "vision.py"
@mcp.tool
def image_info(image_path: str)-> str:
"""
Use this when user wants to know what the image entails
User must have provided the path to where the image is
The image can be on the web via URL or on local machine
Returns the description of the image content
"""
# call the function that will carry this task
try:
# call our "vision.py" function we have imported from tools
result=describe_image(image_path)
# return the result gotten from the model
return result
except Exception as e:
return f"Image Description Error: {e}"
# ----4. Tavily Search Tool
@mcp.tool
def internet_search(
query: str,
max_results: int=5,
topic: Literal["general", "news", "finance"] = "general", # Literal forces LLM to choose 1 from the 3 in the List
include_raw_content:bool=False,
):
"""Run a web search"""
try:
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
except Exception as e:
return f"Web Search Error: {e}"
# ----5. Scrape Webpage and return result in markdown
@mcp.tool
def scrape_webpage(url: str)-> str:
"""
Read a webpage and return clean markdown suitable for an LLM
"""
# Send our request and store the markdown data
response=app.scrape_url(
url=url,
formats=["markdown"]
)
markdown=getattr(response, "markdown", "")
# if the response is empty
if not markdown:
return "Unable to extract content from the webpage."
return f"""
Source URL:
{url}
Extracted Content:
{markdown}
"""
# -----6. Weather forecast
@mcp.tool
def weather_forecast(city: str)-> str:
"""
Use this tool when you have the city/Town name you want to look
up for the current weather conditions
"""
# call our function
try:
result=get_weather(city)
return result
except Exception as e:
return f"Weather tool Error: {e}"
if __name__=="__main__":
# run the MCP server over stdio
mcp.run()
Заданне: запустыце сервер з ручнага кліента без вікорыстання LLM, а пасля спробавайце рэжым HTTP з Cursor або Claude Code для дадатковай практыкі.
Введанне і выведанне голасу
Функцыя перакладу голасу ў тэкст прыводзіць аудыя з мікрафона ў строкі тэксту. Google STT у воблаку — адна з можлівасцей; Whisper і Vosk працуюць офлайн — лепш выбраць Whisper чы Vosk для машынаў з CPU.
Варыянты перакладу тэксту ў голас: pyttsx3 (локальны, робаты стыль, безкоштовны), Piper (локальны нейральны алгорытм, многа мов), Google TTS (у воблаку, часта на базе файлоў), а таксама платныя API, такія як ElevenLabs.
Маленькая дэманстрацыя зафіксавае аудыя з мікрафона за дапамойю распазнавання Google, выконвае простую дзейнасць і выдае голас за дапамойю pyttsx3:
cmd>> uv pip install speech_recognition pyttsx3
#!/usr/bin/python3
# Function processes the user's spoken command as transcribed by Google text to speech
# returns appropriate response
def process_command(command):
# Checked if the user asked about the weather
command=command.lower() # convert the user input to lower case
if "weather" in command:
return "Sir, no storm today. Wear a Tshirt!"
# check if the user requested a joke
elif "joke" in command:
return "Why did the Computer go to therapy? Because it had too many bytes"
# check if the user mentions wikipedia
elif "wikipedia" in command:
return "Sir we have not implemented that, sorry!"
else:
return "Sorry Sir, I didn't understand that"
# We are not using an LLM yet
#!/usr/bin/python3
import speech_recognition as sr
import pyttsx3
# our user defined library actions.py
from actions import process_action
# -----Step 1: Listening your Voice(Speech-to-text)-----
# > Function records audio from the microphone
# > Converts it into text using Google's speech recognition API
def listen():
# create a speech recognizer object
recognizer=sr.Recognizer()
# open the default microphone--> get our voice input
with sr.Microphone() as source:
# Print on terminal recording has started
print("\n\n🎤Listening.....")
# listen until the user stops speaking
# recorded audio is stored in a variable 'audio'
audio=recognizer.listen(source)
# -Since interacting with an API better to use exception handling. Prevents program from crashing
try:
# send the recorded audio to Google's Speech Recognition services
# transcribe our input into text
text=recognizer.recognize_google(audio)
# Display what has been transcribed
print(f"You Said: {text}")
# return text in lower case. Easier to compare it later
return text.lower() #--> now we want to pass it to actions.py
except sr.UnknownValueError:
# if empty whatever was transcribed
return ""
except sr.RequestError:
# incase no internet or googles speech API is unreachable
return "\nError: Check Internet Connection"
# ------Step 2: Talking Back using pyttsx(Text-to-Speech)
def speak(text):
# display what is to be spoken
print(f"JARVIS🤖: {text}")
# To avoid error we create a freshh engine instance every time
engine=pyttsx3.init()
# speed of the audio
engine.setProperty('rate', 170)
# the volume
engine.setProperty('volume', 1)
# execute our text to be spoken now
engine.say(text)
engine.runAndWait()
engine.stop() # clean up after speaking since we are creating a new instance each time
#------Step 3: Execute Everything---------
def main():
speak("Hello Sir, I am JARVIS. How can I help you today? ")
# Create an infinity loop for user interaction
while True:
# get the input voice and convert to text using the listen function(Has our microphone, Google text-to-speech model)
command=listen()
# Check if command is empty we continue--> restarts execution of the infinity loop and skips the other code
if command=="":
continue
# if the command has exit or quit terminate the program
elif "exit" in command or "quit" in command:
speak("Goodbye Sir!")
break
# Now pass our command transcribed to our action.py. Process_action command will execute and return results
response=process_action(command)
#Output the results from our user defined function
speak(response)
# call main function
if __name__=="__main__":
main()
"""
=====================Do Not Add this Part: My results on CMD=======
for_blog>python main.py
JARVIS🤖: Hello Sir, I am JARVIS. How can I help you today?
🎤Listening.....
You Said: what is the weather today
JARVIS🤖: Sir, no storm today. Wear a Tshirt!
🎤Listening.....
You Said: do you do you have Wikipedia
JARVIS🤖: Sir we have not implemented that, sorry!
🎤Listening.....
You Said: tell me a joke
JARVIS🤖: Why did the Computer go to therapy? Because it had too many bytes
🎤Listening.....
You Said: exit
JARVIS🤖: Goodbye Sir!
"""
cmd>> python main.py
Заканчэнне першай часты
Першая часць заканчываецца на інструментах MCP плюс базовы цыкл вводу/выводу голасу. У другай часты будуць включаны LangChain і LangGraph, каб агент могаў коордынаваць гэтыя інструменты з вікорыстаннем памяці і маршрутацыі.
Забезпечэнне надзеі MCP і стаку голасу перад другай часткай
Местныя дэмы маскуюць спосабы абяроны, якія практычна момантальна з’яўляюцца, калі падключаецца другі кліент. За дапамогою транспорту HTTP неабходна ранняя аўтэнтыфікацыя — нават з вікорыстанням спільнага токена-носія — каб публічны тунель не стаў інструментам з вольным доступам. Лічыце назву інструменту, час адклікання і класы бягаў; у голасовых сесіях вельмі складна заўважыць, калі інструмент для аналізу паветра трыццаць секунд застаеся без рэакціі, пакуль корыстнік чакае на мовленне.
Правіла абароны секрэтных даных стаюць ўсё важлівейшымі, калі да калькулятора дадаюцца ключы візыі і пошуку. Не павінны быць включаныя файлы .env у скрыншоты і запісі. Неабходна зміняць ключы, якія вікорыстоўваюцца у навучальных матэрыялах. Валідзіце прынцып мінімальных прав: ключ Firecrawl, які можа скрабаваць толькі дозволеныя домены, ёсць лепшым за токен з рэжымам бога ў студэнтскім рэпазітарыі.
Што стосуецца функцыі голасу, неабяжна на кожны ўзлок выбраць межа афляйн і хмарным рэшэнням. Большасць разрабоў выкарыстоўвае хмарны STT для высокай точнасці, а локальны Piper для TTS, каб ухіліцца ад неабходнасці перадачы і практычна мгновеннага адпаведзення голасам. Неабяжна задокументаваць наследкі для прыватнасці: аудыя, якое выходзіць за межы працуючага апарата, створвае іншыя рызікі, чым тэкстовыя інструменты, якія працуюць праз стандартны ввод-выход. У другай часты гэтыя інструменты будуць абгортаваны ў агента; задача першай часты — зробіць кожны інструмент абсалютна надзеяным у самастойной працэ, каб багі в оркестрацыі не плуталіся з багамі MCP.
Дысцыпліна каталогу інструментаў
Калкулятор, інструменты для перагледу парадактов, пошуку, аналізу веб-сторанак, працы з відобразамі і гадзінкі – калі такі інструменты становяцца ў вельмі большай колькасці, трэба публікаваць односторонній каталог: назву, кароткі апісанне дакумента, неабяжныя зменныя серавайна, прыблізны час адпаведзьбы і інфармацыю пра тое, чы можна без апасцярожнаў выканаць ўжытак інструмента праз HTTP. Цэга прыносіць практычную карысць як агентам, так і людзям. Пад час ручных тэстаў кліента MCP трэба пераканвацца не толькі ў правільной працэсе, але і ў тым, як справа будзе з некорэктнымі аргументамі і порожнімі ключамі API, каб проблемы былі відразу з’яўляліся пад час розрабоцы. Для дэманстрацый голасавага адказу трэба напісаць спіс фіксаваных запытак (“што такое два плюс два”, “працэскароднае становішча ў Найробі”, “падсумаваць гэтую сторанку”) і запісаць апэктыванные последовасці інструментаў. Рэгрэсійныя тэсты станавяцца можлівымі толькі тады, калі працэс голасавага адказу спрацоўвае як тэст інтэграцыі API, які віпадкова выкарыстоўвае мікрафон. Самэ гэтая падготовка ператварае задачу налаштавання LangGraph у другай частцы на проблему складання элементаў, а не на длігі працэс адлучэння бягаў.
Выбор локальнага транспарту для голасовага асістэнта
Stdio захоўвае сервер MCP толькі для працэсу агента і падабяецца да дэманта на ноутбуку, дзе нічога не павінна быць доступная з LAN. Перайдзіце на HTTP толькі тады, калі другі кліент (Cursor, Claude Code, камп’ютар колегі) павінен раздзяляць інструменты, і нават тады прыўяжыце яго да localhost пакуль не будзе створэны рэверсны проксі з аутанацыяй. Сумешчанне способаў перэдачы дадзеных пад час туторыяла без паўтарэння опісу агрэсійнага мадэлю прыводзіць да таго, што чытальнікі пачынаюць нешта адкрываць, включаючы інструменты візуальнага аналізу і пошуку. Ясна пазначайце выбор кожны раз, калі змінюецца вхідная точка сервера, і залейвайце шлях stdio як стандартны для праекту голасовага асістэнта пакуль агент у другай часты не стане стабільным.
Перш чым заканчыць першую частыну, запускіце сервер MCP пад обома спосабамі перэдачы ўтроце і запішыце спіс інструментаў у файл README рэпазітарыя. Будучы вы, які будзе налажваць LangGraph, будзе вдаклаўны ўсёму сябе за тое, што вы не зноў будзіце шукаць, які докстрынг на самай працоўны модэлю патрэбен, каб выбраць Firecrawl замест Tavily.
Таксама пераканайцеся ў правілах на вікорыстоўванне мікрафона і выходу звуку на кожной операцыйнай сістэме, якую вы дэманструюце; багі з TTS часта ствараюцца праз выключаныя прыстроі. Заставьце альтэрнатыўны варыянт, які будзе выводзіць тэкст асістэнта, калі аудыя не працюе, ўпэўніваючыся такім чынам у тым, што семінар застаецца ў руху.
Логістыка семінара, якая спасае сітуацыю
Распрацавайце для учаснікаў одностранковы памятак: як запусціць сервер stdio, як адкрыць кліент, якія ключы .env неабходны для працы з данымі па погодзе чы сяродовішчу, і як выглядае адпаведны спіс інструментаў. Калі хтось застраг, запытайце, чы роўнасць відбуваецца до інструментаў (мікрафон чы STT), у самых інструментах (ключы MCP чы API) чы пасля інструментаў (TTS). Толькі такая дзялёвасць скорачае час на адлучэнне баго напалову і дапамагае працаваць да другай часты.