Reference: MCP Overview
In preparation for building an Obsidian MCP Server, I’m going to follow the official Anthropic guide to build my first MCP server.
This will be a very simple server with two tools: get_alerts and get_forecast. We will connect it to the Claude for Desktop client. MCP supports 3 Primitives, but we will only be implementing Tools
I’m choosing to build in Python, the SDK can be found here.
Logging Best Practice
For STDIO-based servers: Never write to standard output (stdout). This includes:
print()statements in Pythonconsole.log()in JavaScriptfmt.Println()in Go- Similar stdout functions in other languages
# ❌ Bad (STDIO) print("Processing request") # ✅ Good (STDIO) import logging logging.info("Processing request")Writing to stdout will corrupt the JSON-RPC messages and break your server.
Best Practices
- Use a logging library that writes to stderr or files.
- Tool names should follow the format specified here
Setup Env w/ UV
First, let’s install uv (a python package manager)
curl -LsSf https://astral.sh/uv/install.sh | shAnd then set up our environment
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
source .venv/bin/activate
# Install dependencies
uv add "mcp[cli]" httpx
# Create our server file
touch weather.pyFastMCP
For more info on FastMCP
While continuing the guide, I noticed:
# Initialize FastMCP server
mcp = FastMCP("weather")The FastMCP class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain Model Context Protocol tools.
Helper Functions
We added 2 helper functions for querying and formatting data
async def make_nws_request(url: str) -> dict[str], Any] | None:def format_alert(feature: dict) -> str:Tools
Next we implement the logic for the tool using a tool execution handler. We will be implementing 2 tools
@mcp.tool()
async def get_alerts(state: str) -> str:@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:Run server
Finally you just run the server from main method
def main():
# Initialize and run the server
mcp.run(transport='stdio')
if __name__ == "__main__":
main()And then start the MCP server with
uv run weather.pyClient Access w/ Claude Desktop
First, you need to configure Claude for Desktop. To do this, open the config file at ~/Library/Application Support/Claude/claude_desktop_config.json. Create the file if it doesn’t already exist
Then add your server in the mcpServers key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}This tells Claude for Desktop:
- There’s an MCP server named “weather”
- To launch it by running
uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py
FAILED! It did not work, here’s what I saw:

You can navigate to your logs (Found here: /Users/naderbaradar/Library/Logs/Claude) to debug. Here’s what I saw:
naderbaradar@Naders-MacBook-Air Claude % ls
claude.ai-web.log mcp-server-weather.log unknown-window.log
main.log mcp.log window.log
naderbaradar@Naders-MacBook-Air Claude % cat mcp-server-weather.log
2025-10-24T16:29:46.621Z [weather] [info] Initializing server... { metadata: undefined }
2025-10-24T16:29:46.631Z [weather] [info] Using MCP server command: uv with args and path: {
metadata: {
args: [
'--directory',
'/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather',
'run',
'weather.py',
[length]: 4
],
paths: [
'/Users/naderbaradar/.nvm/versions/node/v22.13.0/bin',
'/Users/naderbaradar/.nvm/versions/node/v22.16.0/bin',
'/usr/local/bin',
'/opt/homebrew/bin',
'/usr/bin',
'/usr/bin',
'/bin',
'/usr/sbin',
'/sbin',
[length]: 9
]
}
} %o
2025-10-24T16:29:46.632Z [weather] [error] spawn uv ENOENT {
metadata: {
context: 'connection',
stack: 'Error: spawn uv ENOENT\n' +
' at ChildProcess._handle.onexit (node:internal/child_process:285:19)\n' +
' at onErrorNT (node:internal/child_process:483:16)\n' +
' at process.processTicksAndRejections (node:internal/process/task_queues:90:21)'
}
}Looks like Claude Desktop does not have access to uv so it can’t run the server. Let’s just give it the full path to uv instead since maybe Claude Desktop doesn’t have access to PATH executables due to being a GUI app.

So we’ll just update the JSON entry for the mcp config like so:
{
"mcpServers": {
"weather": {
"command": "/Users/naderbaradar/.local/bin/uv",
"args": [
"--directory",
"/Users/naderbaradar/development_workspace/mcp/servers/build_server_intro_guide/weather",
"run",
"weather.py"
]
}
}
}Now let’s try again…

It works!
