
在前一篇MCP主机使用Cline我们学会了如何使用Cline MCP,本篇我们解析下MCP详细的交互流程。我们知道MCP的通信方式有下面三种:
场景
- 本地集成和命令行工具特别有用;
- 构建命令行工具;
- 本地集成;
- 简单的通信;
- 使用 shell 脚本
场景
- 需要服务端到客户端的流式传输;
- 网络受限
场景
- 高效灵活,支持更大模块的分布式部署
上一节的MCP server的例子是基于STDIO协议的,所以本节我们来分析下STDIO协议下的完整交互流程。为了方便观察整个流程,我们在Cline和MCP server之间架一个STDIO的代理,本质上就是一个管道,伪装成一个MCP server,把Cline的请求转发给MCP server,把MCP server的返回转发给Cline,作为中间商必须要赚差价——记录下往来的消息内容,完整代理内容如下:
#!/usr/bin/env python3
import sys
import subprocess
import threading
import argparse
import os
# --- Configuration ---
LOG_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mcp_io.log")
# --- End Configuration ---
# --- Argument Parsing ---
parser = argparse.ArgumentParser(
description="Wrap a command, passing STDIN/STDOUT verbatim while logging them.",
usage="%(prog)s <command> [args...]"
)
# Capture the command and all subsequent arguments
parser.add_argument('command', nargs=argparse.REMAINDER,
help='The command and its arguments to execute.')
open(LOG_FILE, 'w', encoding='utf-8')
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
if not args.command:
print("Error: No command provided.", file=sys.stderr)
parser.print_help(sys.stderr)
sys.exit(1)
target_command = args.command
# --- End Argument Parsing ---
# --- I/O Forwarding Functions ---
# These will run in separate threads
def forward_and_log_stdin(proxy_stdin, target_stdin, log_file):
"""Reads from proxy's stdin, logs it, writes to target's stdin."""
try:
while True:
# Read line by line from the script's actual stdin
line_bytes = proxy_stdin.readline()
if not line_bytes: # EOF reached
break
# Decode for logging (assuming UTF-8, adjust if needed)
try:
line_str = line_bytes.decode('utf-8')
except UnicodeDecodeError:
line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n" # Log representation
# Log with prefix
log_file.write(f"输入: {line_str}")
log_file.flush() # Ensure log is written promptly
# Write the original bytes to the target process's stdin
target_stdin.write(line_bytes)
target_stdin.flush() # Ensure target receives it promptly
except Exception as e:
# Log errors happening during forwarding
try:
log_file.write(f"!!! STDIN Forwarding Error: {e}\n")
log_file.flush()
except: pass # Avoid errors trying to log errors if log file is broken
finally:
# Important: Close the target's stdin when proxy's stdin closes
# This signals EOF to the target process (like test.sh's read loop)
try:
target_stdin.close()
log_file.write("--- STDIN stream closed to target ---\n")
log_file.flush()
except Exception as e:
try:
log_file.write(f"!!! Error closing target STDIN: {e}\n")
log_file.flush()
except: pass
def forward_and_log_stdout(target_stdout, proxy_stdout, log_file):
"""Reads from target's stdout, logs it, writes to proxy's stdout."""
try:
while True:
# Read line by line from the target process's stdout
line_bytes = target_stdout.readline()
if not line_bytes: # EOF reached (process exited or closed stdout)
break
# Decode for logging
try:
line_str = line_bytes.decode('utf-8')
except UnicodeDecodeError:
line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n"
# Log with prefix
log_file.write(f"输出: {line_str}")
log_file.flush()
# Write the original bytes to the script's actual stdout
proxy_stdout.write(line_bytes)
proxy_stdout.flush() # Ensure output is seen promptly
except Exception as e:
try:
log_file.write(f"!!! STDOUT Forwarding Error: {e}\n")
log_file.flush()
except: pass
finally:
try:
log_file.flush()
except: pass
# Don't close proxy_stdout (sys.stdout) here
# --- Main Execution ---
process = None
log_f = None
exit_code = 1 # Default exit code in case of early failure
try:
# Open log file in append mode ('a') for the threads
log_f = open(LOG_FILE, 'a', encoding='utf-8')
# Start the target process
# We use pipes for stdin/stdout
# We work with bytes (bufsize=0 for unbuffered binary, readline() still works)
# stderr=subprocess.PIPE could be added to capture stderr too if needed.
process = subprocess.Popen(
target_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, # Capture stderr too, good practice
bufsize=0 # Use 0 for unbuffered binary I/O
)
# Pass binary streams to threads
stdin_thread = threading.Thread(
target=forward_and_log_stdin,
args=(sys.stdin.buffer, process.stdin, log_f),
daemon=True # Allows main thread to exit even if this is stuck (e.g., waiting on stdin) - reconsider if explicit join is needed
)
stdout_thread = threading.Thread(
target=forward_and_log_stdout,
args=(process.stdout, sys.stdout.buffer, log_f),
daemon=True
)
# Optional: Handle stderr similarly (log and pass through)
stderr_thread = threading.Thread(
target=forward_and_log_stdout, # Can reuse the function
args=(process.stderr, sys.stderr.buffer, log_f), # Pass stderr streams
# Add a different prefix in the function if needed, or modify function
# For now, it will log with "STDOUT:" prefix - might want to change function
# Let's modify the function slightly for this
daemon=True
)
# A slightly modified version for stderr logging
def forward_and_log_stderr(target_stderr, proxy_stderr, log_file):
"""Reads from target's stderr, logs it, writes to proxy's stderr."""
try:
while True:
line_bytes = target_stderr.readline()
if not line_bytes: break
try: line_str = line_bytes.decode('utf-8')
except UnicodeDecodeError: line_str = f"[Non-UTF8 data, {len(line_bytes)} bytes]\n"
log_file.write(f"STDERR: {line_str}") # Use STDERR prefix
log_file.flush()
proxy_stderr.write(line_bytes)
proxy_stderr.flush()
except Exception as e:
try:
log_file.write(f"!!! STDERR Forwarding Error: {e}\n")
log_file.flush()
except: pass
finally:
try:
log_file.flush()
except: pass
stderr_thread = threading.Thread(
target=forward_and_log_stderr,
args=(process.stderr, sys.stderr.buffer, log_f),
daemon=True
)
# Start the forwarding threads
stdin_thread.start()
stdout_thread.start()
stderr_thread.start() # Start stderr thread too
# Wait for the target process to complete
process.wait()
exit_code = process.returncode
# Wait briefly for I/O threads to finish flushing last messages
# Since they are daemons, they might exit abruptly with the main thread.
# Joining them ensures cleaner shutdown and logging.
# We need to make sure the pipes are closed so the reads terminate.
# process.wait() ensures target process is dead, pipes should close naturally.
stdin_thread.join(timeout=1.0) # Add timeout in case thread hangs
stdout_thread.join(timeout=1.0)
stderr_thread.join(timeout=1.0)
except Exception as e:
print(f"MCP Logger Error: {e}", file=sys.stderr)
# Try to log the error too
if log_f and not log_f.closed:
try:
log_f.write(f"!!! MCP Logger Main Error: {e}\n")
log_f.flush()
except: pass # Ignore errors during final logging attempt
exit_code = 1 # Indicate logger failure
finally:
# Ensure the process is terminated if it's still running (e.g., if logger crashed)
if process and process.poll() is None:
try:
process.terminate()
process.wait(timeout=1.0) # Give it a moment to terminate
except: pass # Ignore errors during cleanup
if process.poll() is None: # Still running?
try: process.kill() # Force kill
except: pass # Ignore kill errors
# Final log message
if log_f and not log_f.closed:
try:
log_f.close()
except: pass # Ignore errors during final logging attempt
# Exit with the target process's exit code
sys.exit(exit_code)然后,启动我们的代理和MCP server
% python3 mcp_logger.py uv run weather.py 改下Cline的MCP配置
{
"mcpServers": {
"weather": {
"disabled": false,
"timeout": 60,
"command": "uv",
"args": [
"--directory",
"/Users/xiazemin/py/weather",
"run",
"mcp_logger.py",
"uv",
"--directory",
"/Users/xiazemin/py/weather",
"run",
"weather.py"
],
"transportType": "stdio"
}
}
}配置完成后Cline就会和代理建立连接,就会在mcp_io.log里记录交互的日志
输入: {"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"Cline","version":"3.17.5"}},"jsonrpc":"2.0","id":0}
输出: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.6.0"}}}
输入: {"method":"notifications/initialized","jsonrpc":"2.0"}
输入: {"method":"tools/list","jsonrpc":"2.0","id":1}
输出: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\nArgs:\n state: Two-letter US state code (e.g. CA, NY)\n","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\nArgs:\n latitude: Latitude of the location\n longitude: Longitude of the location\n","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"}}]}}
输入: {"method":"resources/list","jsonrpc":"2.0","id":2}
输出: {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}
输入: {"method":"resources/templates/list","jsonrpc":"2.0","id":3}
输出: {"jsonrpc":"2.0","id":3,"result":{"resourceTemplates":[]}}1,Cline给MCP server发送协议版本等信息
2,MCP server会响应版本信息进行确认
3,Cline 发送信息notifications/initialized,表示初始化连接完成,整体类似tcp的三次握手。
4,然后Cline发送信息tools/list询问MCP server支持哪些工具
5,MCP server回复工具列表get_alerts和get_forecast,并使用json-schema格式提供工具的参数说明和工具的介绍信息,方便LLM进行决策和使用。
6,Cline发送resources/list 指令,询问资源列表
7,由于我们的MCP server没有实现资源服务,所以返回空
8,Cline发送resources/templates/list指令询问资源模板列表
9,由于同样没有实现,返回空
这样完整的MCP主机和MCP server建立连接的过程就完成啦。

接着MCP主机会把上面的MCP server提供的能力作为提示词的一部分发送给LLM,LLM判断需要调用MCP工具,就返回给Cline,Cline会给我们一个提示框,我们点击approve后,会调用MCP server,结下来看看这一部分的流程:
输入: {"method":"tools/call","params":{"name":"get_alerts","arguments":{"state":"TX"}},"jsonrpc":"2.0","id":4}
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* 输出: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"\nEvent: Rip Current Statement\nArea: Matagorda Islands; Brazoria Islands; Galveston Island; Bolivar Peninsula\nSeverity: Moderate\nDescription: * WHAT...Dangerous rip currents.\n\n* WHERE...Gulf-facing beaches, including the Matagorda\nPeninsula, Brazoria County beaches, Galveston Island and the\nBolivar Peninsula.\n\n* WHEN...Through Tuesday morning.\n\n* IMPACTS...Rip currents can sweep even the best swimmers away\nfrom shore into deeper water.\n\n* ADDITIONAL DETAILS...Moderate risk of rip current early today\nwill gradually increase to a high risk through the day, and will\npersist into the upcoming week. Use extra caution this holiday weekend.\nStay aware of the beach flags and listen to lifeguards.\nInstructions: Swim near a lifeguard and away from rocks, jetties, and piers. If\ncaught in a rip current, relax and float. Don't swim against the\ncurrent. If able, swim in a direction following the shoreline. If\nunable to escape, face the shore and call or wave for help.\n\n---\n\nEvent: Severe Thunderstorm Watch\nArea: Columbia, AR; Hempstead, AR; Howard, AR; Lafayette, AR; Little River, AR; Miller, AR; Nevada, AR; Sevier, AR; Union, AR; McCurtain, OK; Bowie, TX\nSeverity: Severe\nDescription: THE NATIONAL WEATHER SERVICE HAS ISSUED SEVERE THUNDERSTORM WATCH\n316 IN EFFECT UNTIL 3 PM CDT THIS AFTERNOON FOR THE FOLLOWING\nAREAS\n\nIN ARKANSAS THIS WATCH INCLUDES 9 COUNTIES\n\nIN SOUTH CENTRAL ARKANSAS\n\nUNION\n\nIN SOUTHWEST ARKANSAS\n\nCOLUMBIA HEMPSTEAD HOWARD\nLAFAYETTE LITTLE RIVER MILLER\nNEVADA SEVIER\n\nIN OKLAHOMA THIS WATCH INCLUDES 1 COUNTY\n\nIN SOUTHEAST OKLAHOMA\n\nMCCURTAIN\n\nIN TEXAS THIS WATCH INCLUDES 1 COUNTY\n\nIN NORTHEAST TEXAS\n\nBOWIE\n\nTHIS INCLUDES THE CITIES OF ASHDOWN, BRADLEY, BROKEN BOW,\nDE QUEEN, DIERKS, EL DORADO, HOPE, IDABEL, LEWISVILLE, MAGNOLIA,\nMINERAL SPRINGS, NASHVILLE, PRESCOTT, STAMPS, TEXARKANA,\nAND TEXARKANA.\nInstructions: None\n\n---\n\nEvent: Flood Warning\nArea: Angelina, TX; Houston, TX; Polk, TX; Trinity, TX; Tyler, TX\nSeverity: Severe\nDescription: ...The Flood Warning is extended for the following rivers in Texas...\n\nNeches River Near Diboll affecting Houston, Angelina, Trinity,\nPolk and Tyler Counties.\n\nFor the Neches River...including Diboll...Minor flooding is forecast.\n\n* WHAT...Minor flooding is occurring and minor flooding is forecast.\n\n* WHERE...Neches River near Diboll.\n\n* WHEN...Until Monday afternoon.\n\n* IMPACTS...At 12.0 feet, Minor lowland flooding occurs. Expect\nflooded boat ramps and trails.\n\n* ADDITIONAL DETAILS...\n- At 8:15 AM CDT Saturday the stage was 12.4 feet.\n- Bankfull stage is 12.0 feet.\n- Recent Activity...The maximum river stage in the 24 hours\nending at 8:15 AM CDT Saturday was 12.6 feet.\n- Forecast...The river is expected to fall below flood stage\nSunday evening and continue falling to 10.7 feet Thursday\nmorning.\n- Flood stage is 12.0 feet.\n- http://www.weather.gov/safety/flood\nInstructions: Do not drive cars through flooded areas.\nCaution is urged when walking near riverbanks.\nTurn around, don't drown when encountering flooded roads. Most flood\ndeaths occur in vehicles.\n\nMotorists should not attempt to drive around barricades or drive\ncars through flooded areas.\n\nFor more hydrologic information, copy and paste the following website\naddress into your favorite web browser URL bar:\nhttps://water.noaa.gov/wfo/SHV\n\nThe next statement will be issued Sunday morning at 845 AM CDT.\n\n---\n\nEvent: Heat Advisory\nArea: Jim Wells; Inland Kleberg; Inland Nueces; Inland San Patricio\nSeverity: Moderate\nDescription: * WHAT...Heat index values up to 111 expected.\n\n* WHERE...Inland Kleberg, Inland Nueces, Inland San Patricio, and\nJim Wells Counties.\n\n* WHEN...From 1 PM this afternoon to 7 PM CDT this evening.\n\n* IMPACTS...Hot temperatures and high humidity may cause heat\nillnesses.\nInstructions: Drink plenty of fluids, stay in an air-conditioned room, stay out of\nthe sun, and check up on relatives and neighbors.\n\n---\n\nEvent: Flood Warning\nArea: Beauregard, LA; Calcasieu, LA; Newton, TX; Orange, TX\nSeverity: Severe\nDescription: ...The Flood Warning continues for the following rivers in Texas...\nLouisiana...\n\nSabine River Near Deweyville\n\nAdditional information is available at www.weather.gov.\n\n* WHAT...Minor flooding is occurring and minor flooding is forecast.\n\n* WHERE...Sabine River near Deweyville.\n\n* WHEN...Until further notice.\n\n* IMPACTS...At 24.0 feet, Minor lowland flooding will occur.\n\n* ADDITIONAL DETAILS...\n- At 6:45 PM CDT Friday the stage was 24.4 feet.\n- Recent Activity...The maximum river stage in the 24 hours\nending at 6:45 PM CDT Friday was 24.4 feet.\n- Forecast...The river is expected to rise to a crest of 24.4\nfeet just after midnight tonight.\n- Flood stage is 24.0 feet.\n- http://www.weather.gov/safety/flood\nInstructions: None\n"}],"isError":false}}
*/1,Cline发起请求tools/call
2,MCP server响应请求结果
完成MCP调用后,Cline还会把结果交给LLM进行总结,最后返回给Cline。
下面我们详细分析下获取工具列表和和调用MCP的详细内容。获取工具列表的请求如下:
{"method": "tools/list","jsonrpc": "2.0","id": 1}返回值,里面包含工具的name,description,inputSchema等完的参数说明。
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_alerts",
"description": "Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
",
"inputSchema": {
"properties": {
"state": {
"title": "State",
"type": "string"
}
},
"required": [
"state"
],
"title": "get_alertsArguments",
"type": "object"
}
},
{
"name": "get_forecast",
"description": "Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
",
"inputSchema": {
"properties": {
"latitude": {
"title": "Latitude",
"type": "number"
},
"longitude": {
"title": "Longitude",
"type": "number"
}
},
"required": [
"latitude",
"longitude"
],
"title": "get_forecastArguments",
"type": "object"
}
}
]
}
}调用工具的输入包括工具名称和参数state
{
"method": "tools/call",
"params": {
"name": "get_alerts",
"arguments": {
"state": "TX"
}
},
"jsonrpc": "2.0",
"id": 4
}返回如下,content的text的字段就是mcp的srver的返回:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "
Event: Rip Current Statement
Area: Matagorda Islands; Brazoria Islands; Galveston Island; Bolivar Peninsula
Severity: Moderate
Description: * WHAT...Dangerous rip currents.
* WHERE...Gulf-facing beaches, including the Matagorda
Peninsula, Brazoria County beaches, Galveston Island and the
Bolivar Peninsula.
* WHEN...Through Tuesday morning.
* IMPACTS...Rip currents can sweep even the best swimmers away
from shore into deeper water.
* ADDITIONAL DETAILS...Moderate risk of rip current early today
will gradually increase to a high risk through the day, and will
persist into the upcoming week. Use extra caution this holiday weekend.
Stay aware of the beach flags and listen to lifeguards.
Instructions: Swim near a lifeguard and away from rocks, jetties, and piers. If
caught in a rip current, relax and float. Don't swim against the
current. If able, swim in a direction following the shoreline. If
unable to escape, face the shore and call or wave for help.
---
Event: Severe Thunderstorm Watch
Area: Columbia, AR; Hempstead, AR; Howard, AR; Lafayette, AR; Little River, AR; Miller, AR; Nevada, AR; Sevier, AR; Union, AR; McCurtain, OK; Bowie, TX
Severity: Severe
Description: THE NATIONAL WEATHER SERVICE HAS ISSUED SEVERE THUNDERSTORM WATCH
316 IN EFFECT UNTIL 3 PM CDT THIS AFTERNOON FOR THE FOLLOWING
AREAS
IN ARKANSAS THIS WATCH INCLUDES 9 COUNTIES
IN SOUTH CENTRAL ARKANSAS
UNION
IN SOUTHWEST ARKANSAS
COLUMBIA HEMPSTEAD HOWARD
LAFAYETTE LITTLE RIVER MILLER
NEVADA SEVIER
IN OKLAHOMA THIS WATCH INCLUDES 1 COUNTY
IN SOUTHEAST OKLAHOMA
MCCURTAIN
IN TEXAS THIS WATCH INCLUDES 1 COUNTY
IN NORTHEAST TEXAS
BOWIE
THIS INCLUDES THE CITIES OF ASHDOWN, BRADLEY, BROKEN BOW,
DE QUEEN, DIERKS, EL DORADO, HOPE, IDABEL, LEWISVILLE, MAGNOLIA,
MINERAL SPRINGS, NASHVILLE, PRESCOTT, STAMPS, TEXARKANA,
AND TEXARKANA.
Instructions: None
---
Event: Flood Warning
Area: Angelina, TX; Houston, TX; Polk, TX; Trinity, TX; Tyler, TX
Severity: Severe
Description: ...The Flood Warning is extended for the following rivers in Texas...
Neches River Near Diboll affecting Houston, Angelina, Trinity,
Polk and Tyler Counties.
For the Neches River...including Diboll...Minor flooding is forecast.
* WHAT...Minor flooding is occurring and minor flooding is forecast.
* WHERE...Neches River near Diboll.
* WHEN...Until Monday afternoon.
* IMPACTS...At 12.0 feet, Minor lowland flooding occurs. Expect
flooded boat ramps and trails.
* ADDITIONAL DETAILS...
- At 8:15 AM CDT Saturday the stage was 12.4 feet.
- Bankfull stage is 12.0 feet.
- Recent Activity...The maximum river stage in the 24 hours
ending at 8:15 AM CDT Saturday was 12.6 feet.
- Forecast...The river is expected to fall below flood stage
Sunday evening and continue falling to 10.7 feet Thursday
morning.
- Flood stage is 12.0 feet.
- http://www.weather.gov/safety/flood
Instructions: Do not drive cars through flooded areas.
Caution is urged when walking near riverbanks.
Turn around, don't drown when encountering flooded roads. Most flood
deaths occur in vehicles.
Motorists should not attempt to drive around barricades or drive
cars through flooded areas.
For more hydrologic information, copy and paste the following website
address into your favorite web browser URL bar:
https://water.noaa.gov/wfo/SHV
The next statement will be issued Sunday morning at 845 AM CDT.
---
Event: Heat Advisory
Area: Jim Wells; Inland Kleberg; Inland Nueces; Inland San Patricio
Severity: Moderate
Description: * WHAT...Heat index values up to 111 expected.
* WHERE...Inland Kleberg, Inland Nueces, Inland San Patricio, and
Jim Wells Counties.
* WHEN...From 1 PM this afternoon to 7 PM CDT this evening.
* IMPACTS...Hot temperatures and high humidity may cause heat
illnesses.
Instructions: Drink plenty of fluids, stay in an air-conditioned room, stay out of
the sun, and check up on relatives and neighbors.
---
Event: Flood Warning
Area: Beauregard, LA; Calcasieu, LA; Newton, TX; Orange, TX
Severity: Severe
Description: ...The Flood Warning continues for the following rivers in Texas...
Louisiana...
Sabine River Near Deweyville
Additional information is available at www.weather.gov.
* WHAT...Minor flooding is occurring and minor flooding is forecast.
* WHERE...Sabine River near Deweyville.
* WHEN...Until further notice.
* IMPACTS...At 24.0 feet, Minor lowland flooding will occur.
* ADDITIONAL DETAILS...
- At 6:45 PM CDT Friday the stage was 24.4 feet.
- Recent Activity...The maximum river stage in the 24 hours
ending at 6:45 PM CDT Friday was 24.4 feet.
- Forecast...The river is expected to rise to a crest of 24.4
feet just after midnight tonight.
- Flood stage is 24.0 feet.
- http://www.weather.gov/safety/flood
Instructions: None
"
}
],
"isError": false
}
}本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!