MCP实战:从零开始写基于 Python 的 MCP 服务(附源码)

项目源码地址:obsidian-mcp-python

介绍

现在mcp相关信息满天飞,但大多都在搞理论。不如动手搞个mcp服务,让它跑起来才能真正感受到mcp是个啥。另外,目前的免费教程大多数是用 js/ts 写的,python版本还真是少之又少。刚好自己第一个mcp服务是python写的,虽然小但也方便理解。重点即便今天搜了mcp python相关资料,依旧很少,那就水一篇吧。后续会系统写下如何基于fastMCP开发MCP服务。

开发

MCP 前置知识

关于什么是mcp,太多人讲了,可以自行查下。我这里也推荐几篇,其中的实战是基于js的,不过不妨碍整理理解mcp的核心概念和能力

Python 开发工具

如果是第一次使用python的话,工具篇可以直接选uv,类似JS生态中的npm,关于 UV,可以看下我写的这个:Python管理工具UV

python中有 虚拟环境 的概念对于前端同学可能比较陌生,简单讲,虚拟环境 相当于nvm + npm 管理下的前端项目,一个python项目跑在一个指定的虚拟环境下,有指定的python版本和依赖库,这样可以使得不同项目有自己专门的python版本和依赖库版本。

MCP 服务开发

先简单介绍下该mcp的功能:可以操作obsidian中的markdown文件,如读取文件数量、返回文件内容、新建文件。

下面是具体开发过程:

  • pip install uv 安装uv工具
  • uv init obsidian-mcp-python 创建项目目录
  • cd obsidian-mcp-python
  • uv venv 新建虚拟环境
  • source .venv/bin/activate 开启虚拟环境
  • uv add "mcp[cli]" 安装指定依赖,mcp这个包提供了fastMCP FastMCP是官方推荐的一个基于python的mcp开发框架,使用类似nestjs中装饰器的风格提供tools、resources、prompt等能力,不过我们这个例子中只设计tools的使用。
  • 新建一个main.py文件,所有的功能都在里面了。
python 复制代码
import os
import glob
from mcp.server.fastmcp import FastMCP
mcp = FastMCP()

# 从环境变量中获取 Obsidian 根目录路径,默认为当前目录
OBSIDIAN_ROOT = os.environ.get("OBSIDIAN_PATH", ".")

@mcp.tool()
def count_markdown_files():
	"""获取 Obsidian 库中所有 Markdown 文件的数量"""
	md_files = glob.glob(os.path.join(OBSIDIAN_ROOT, "**/*.md"), recursive=True)
	return len(md_files)

@mcp.tool()
def get_all_markdown_contents():
	"""获取 Obsidian 库中所有 Markdown 文件的内容"""
	md_files = glob.glob(os.path.join(OBSIDIAN_ROOT, "**/*.md"), recursive=True)
	result = []
	for file_path in md_files:
		try:
			with open(file_path, 'r', encoding='utf-8') as f:
			content = f.read()
			# 获取相对路径作为文件标识符
			relative_path = os.path.relpath(file_path, OBSIDIAN_ROOT)
			result.append({
				"file": relative_path,
				"content": content
			})
		except Exception as e:
			result.append({
				"file": os.path.relpath(file_path, OBSIDIAN_ROOT),
				"error": str(e)
			})
	return result


@mcp.tool()
def create_markdown_file(filename, content=""):
	"""在 Obsidian 库中创建一个新的 Markdown 文件
	
	Args:
		filename: Markdown 文件名 (不需要 .md 后缀,会自动添加)
		content: 文件的初始内容 (可选)
	"""

	# 确保有 .md 后缀
	if not filename.endswith('.md'):
		filename += '.md'
	
	# 创建完整路径
	file_path = os.path.join(OBSIDIAN_ROOT, filename)
	# 检查文件是否已存在
	if os.path.exists(file_path):
		return {"success": False, "error": f"文件 {filename} 已存在"}
	
	try:
		# 确保目录存在
		os.makedirs(os.path.dirname(file_path), exist_ok=True)
		# 创建文件
		with open(file_path, 'w', encoding='utf-8') as f:
		f.write(content)
		return {"success": True, "path": os.path.relpath(file_path, OBSIDIAN_ROOT)}

	except Exception as e:
		return {"success": False, "error": str(e)}

if __name__ == "__main__":
	print(f"Obsidian MCP 服务已启动,使用路径: {OBSIDIAN_ROOT}")
	mcp.run(transport="stdio")

现在我们借助 @mcp.tool() 写了3个工具函数,这几个工具是让llm去调的,比如在cursor的ai会话中,当你让他操作obsidian时,他会自动找到可用的mcp,判断应该调哪个工具。

接下来就是写mcp 的配置json,我就把我自己配的原样输出了(更便于理解),地址部分改成自己本地地址就行

json 复制代码
{
	"mcpServers": {
		"obsidian-mcp-python": {
			"command": "uv",
			"args": [
				"--directory",
				"/Users/ran/Code/Github/zhangran/obsidian-mcp-python",
				"run",
				"main.py"
			],
			"env": {
				"OBSIDIAN_PATH": "/Users/ran/Documents/ObsidianNotes"
			}
		}
	}
}

使用

上面的配置就是适合cursor的,直接copy下贴到cursor的mcp配置里就行。

简单贴下效果:

可以看出能调用查询和新建工具了:count_markdown_files create_markdown_file

然后再打开我的obsidian,发现文章已经写进来了,如下图:

结束~

参考的几个mcp相关资料 FastMCP框架 FastMCP 官网Welcome to FastMCP 2.0 FastMCP脚手架 fastmcp-boilerplate: 简单的FastMCP脚手架基于Typescript FastMCP Typescript版 mcp-obsidian: 一个基于restful的obsidian mcp server

相关推荐
点云SLAM1 小时前
Pytorch中gather()函数详解和实战示例
人工智能·pytorch·python·深度学习·机器学习·计算视觉·gather函数
无影无踪的青蛙1 小时前
[Python][Flask][Gunicorn] 搭建一个服务器-初步-小白式教程 - 1
python·flask·gunicorn
老歌老听老掉牙2 小时前
条件向量运算与三元表达式
python·向量·sympy·三元表达式·条件
乐言z2 小时前
SenseVoice部署,并调用api接口
python·语音识别·语音转文字
Sapphire~2 小时前
odoo-054 one2many 字段新增时检查上一行某个字段是否填写
python·odoo
爱喝阔落的猫3 小时前
【JVM 07-运行时常量池重要组成部分-StringTable】
开发语言·jvm·python
狐凄3 小时前
Python实例题:图片批量处理工具
开发语言·python
天机️灵韵3 小时前
谷歌时间序列算法:零样本预测如何重塑行业决策?
人工智能·python·算法·开源项目
晨曦5432103 小时前
Flask入门指南:从零构建Python微服务
python·学习·flask
神仙别闹4 小时前
基于Python实现自然语言处理(主题层次的情感分类)
python·自然语言处理·分类