本文介绍了如何使用Model Context Protocol(MCP)构建能够与Solana区块链交互的AI助手。通过创建Solana MCP服务器,AI助手能够查询钱包余额、查看Token账户、检索交易详情和分析账户信息。文章详细阐述了服务器的搭建步骤,包括环境配置、工具创建、资源添加和提示设置,并提供了增强服务器功能的建议,如集成Token元数据、价格信息和交易历史。
模型上下文协议(MCP) 允许你使用与外部数据源和服务进行交互的自定义工具来扩展像 Claude 这样的 AI 助手。在本指南中,你将构建一个 Solana MCP 服务器,使 Claude Desktop (或 Cursor) 等工具能够直接查询 Solana 区块链。这种集成将使 Claude 能够执行诸如检查钱包余额、查看 Token 账户、检索交易详情和分析账户信息等任务——所有这些都在你对这些交互的控制之下。 有很多很棒的工具连接了 AI 和 Solana,比如 Goat SDK 和 SendAI.fun,但如果你想构建自己的工具,本指南非常适合你!在本教程结束时,你将拥有一个功能齐全的 Solana MCP 服务器,你可以将其连接到 Claude Desktop,从而通过自然语言实现无缝的区块链交互:
依赖 | 版本 |
---|---|
node | >=23.0.0 |
@modelcontextprotocol/sdk | ^1.9.0 |
@solana/kit | ^2.1.0 |
zod | ^3.24.2 |
typescript | ^5.8.3 |
Claude Desktop | 0.9.2 |
模型上下文协议 (MCP) 是一个开放协议,它弥合了 AI 模型和外部数据源或工具之间的差距。它为应用程序提供上下文给大型语言模型 (LLM) 创建了一种标准化方式,使它们能够访问实时信息并执行超出其训练数据范围的操作。可以将 MCP 视为一个通用适配器,允许 AI 模型安全可靠地与各种数据源、API 和功能进行交互。 MCP 的核心遵循客户端-服务器架构,其中像 Claude 这样的 AI 应用程序充当连接到 MCP 服务器的客户端。这些服务器暴露三个主要功能:
无论你正在构建数据可视化工具、代码分析器还是与外部系统的接口,MCP 都提供了一个结构化的、安全的框架,用于扩展 AI 功能。这对于使区块链数据更易于访问和使用尤其方便。
模型上下文协议支持 AI 助手和 QuickNode 等外部服务之间的安全通信,从而能够访问 Solana(或任何其他区块链)数据:
对于我们的 Solana MCP,我们将主要关注使用 RPC 调用的工具,但我们还将实现一个资源和一些提示,以演示完整的集成。让我们开始构建吧!
让我们首先创建一个新项目并安装必要的依赖项:
mkdir solana-mcp && cd solana-mcp
初始化一个新的 npm 项目
npm init -y
安装依赖
npm install @modelcontextprotocol/sdk @solana/kit zod
npm install --save-dev typescript @types/node
创建一个具有以下配置的 tsconfig.json
文件:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"outDir": "./build",
"rootDir": "./",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["*.ts"],
"exclude": ["node_modules", "build"]
}
确保你的 package.json
中有这些脚本:
"scripts": {
"build": "tsc",
"start": "node build/index.js"
}
创建一个新目录 src
并在其中创建 index.ts
文件:
mkdir src && touch src/index.ts
让我们首先实现一个工具,它将允许我们的客户端获取钱包的 SOL 余额。
打开 src/index.ts
并添加以下代码:
import {
McpServer,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
createSolanaRpc,
address,
isSolanaError,
assertIsAddress,
assertIsSignature,
} from "@solana/kit";
在这里,我们导入必要的依赖项来创建我们的 MCP 服务器。@modelcontextprotocol/sdk
包提供了构建 MCP 服务器的核心功能,而 @solana/kit
提供了用于处理 Solana RPC 调用的实用程序。我们还导入 zod
进行模式验证,这将有助于服务器验证输入和输出。
接下来,我们需要为我们的服务器定义一些常量。这包括 RPC 端点和 SPL 程序密钥。将以下代码添加到 src/index.ts
:
const CONFIG = {
rpcEndpoint:
process.env.SOLANA_RPC_ENDPOINT || "https://api.mainnet-beta.solana.com",
};
const SPL_PROGRAM_KEYS = {
TOKEN_PROGRAM: address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
TOKEN_2022_PROGRAM: address("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"),
};
const solanaRpc = createSolanaRpc(CONFIG.rpcEndpoint);
你会注意到,我们正在引用环境变量而不使用像 dotenv
这样的库。MCP 服务器将加载客户端配置的环境变量(例如,claude_desktop_config.json
)。我们稍后会进行设置。
现在,让我们创建 MCP 服务器。此服务器将处理来自客户端的传入请求,并以适当的数据进行响应。将以下代码添加到 src/index.ts
:
const server = new McpServer({
name: "SolanaMCP",
version: "1.0.0",
});
我们的新变量 server
启动 McpServer
类的一个新实例。name
和 version
属性用于标识服务器及其版本。现在我们只需要定义我们的服务器将暴露给客户端的工具、资源和提示即可。我们可以简单地使用 server.tool()
方法(或根据需要使用 .resource()
或 .prompt()
)来做到这一点。
我们将创建的第一个工具是一个简单的余额检查器。该工具将采用一个 Solana 地址作为输入,并返回该地址的余额。将以下代码添加到 src/index.ts
:
server.tool(
"getBalance",
{
walletAddress: z
.string()
.describe("Solana wallet address to check the balance for"),
},
async (args: { walletAddress: string }) => {
try {
assertIsAddress(args.walletAddress);
const accountAddress = address(args.walletAddress);
const { value: lamports } = await solanaRpc
.getBalance(accountAddress)
.send();
const solBalance = Number(lamports) / 1_000_000_000;
return {
content: [\
{\
type: "text" as const,\
text: `Balance for ${args.walletAddress}: ${solBalance} SOL (${lamports.toString()} lamports)`,\
},\
],
};
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `Error while getting balance: ${isSolanaError(error) ? error.message : "Unknown error"}`,\
},\
],
isError: true,
};
}
},
);
此代码定义了一个名为 getBalance
的新工具。该工具采用一个参数 walletAddress
,它是一个表示要检查余额的 Solana 地址的字符串。tool()
方法采用三个参数:
回调函数使用 @solana/kit
中的 assertIsAddress
函数来验证地址格式。如果地址有效,它将调用 Solana RPC 客户端上的 getBalance
方法并返回 SOL 和 lamports 的余额。如果发生错误,它将返回一条错误消息。
太棒了!我们现在已经设置了服务器的基础知识。接下来,我们需要初始化服务器并开始监听传入请求。将以下代码添加到 src/index.ts
:
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
runServer().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
我们正在使用标准输入/输出类来处理传入请求(推荐用于本地集成)。此传输允许服务器通过标准输入和输出与客户端通信。connect()
方法启动服务器并开始侦听传入请求。随着你使用 MCP 构建更多内容,你可能需要其他传输选项——有关更多详细信息,你可以参考 MCP SDK 文档。
现在我们已经设置好服务器,让我们构建并运行它。在你的终端中,运行以下命令:
npm run build
这将把你的 TypeScript 代码编译成 JavaScript,并将输出放在 build
目录中。我们现在可以运行服务器,但使用 Claude Desktop——我们实际上不需要这样做,因为它会为我们初始化服务器。
现在我们已经编译了 MCP 服务器,我们需要确保 Claude 可以找到它并配置为使用它。确保你已安装 Claude Desktop。从应用程序中,转到 "Claude" -> "设置" 并点击 "开发者"。点击 "编辑配置":
这应该会打开一个 claude_desktop_config.json
文件(或其父目录)。如果你没有看到该文件,请查看最新的 参考文档 以查看它的位置。
你应该会看到一个 MCP Severs 的 JSON 对象。如果这是你的第一个 MCP--它应该是空的。我们将向 mcpServers
对象添加一个新的 solana
对象。此对象将包含我们的 Solana MCP 服务器的配置。使用以下内容更新 claude_desktop_config.json
:
{
"mcpServers": {
"solana": {
"command": "node",
"args": [\
"/absolute/path/to/build/index.js"\
],
"env": {
"SOLANA_RPC_ENDPOINT": "https://example.solana-mainnet.quiknode.pro/123456/"
}
}
// other MCP servers that you already have configured...
}
}
请确保:
/absolute/path/to/build/index.js
替换为你的 build/index.js
文件的绝对路径。你可以通过在终端中运行 pwd
找到它。https://example.solana-mainnet.quiknode.pro/123456/
替换为你自己的 Solana RPC 端点。如果你没有,你可以 在此处 免费获取一个。注意:这是定义 SOLANA_RPC_ENDPOINT
环境变量的地方。有关 MCP 服务器中环境变量的更多信息,请查看 官方文档。
保存并关闭文件。在本指南中,你无需进一步编辑此文件。
现在我们已经更新了 Claude Config,你需要关闭并重新打开 Claude Desktop。这样做,并在重新启动时,Claude Desktop 应该启动你的 MCP 服务器。你应该在开始时看到类似这样的内容:
点击 "🔨 工具" 按钮,你应该会看到你可以使用的可用工具(在我们的例子中,getBalance
)👀:
让我们测试一下。继续让 Claude 获取你的钱包的余额(例如,"CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM 的余额是多少?")。思考片刻后,Claude 应该请求允许使用 MCP 工具:
点击以允许使用该工具,Claude 应该从区块链中获取数据并在你的响应中返回它:
恭喜!你已成功创建了一个基本的 Solana MCP 服务器并将其与 Claude Desktop 集成。
现在基本集成正在工作,让我们用更多功能增强我们的服务器。我们将添加:
添加这些功能非常简单——我们所要做的就是通过单个方法调用注册每个功能。让我们逐一介绍——随意跳过并只实现你想要的功能。
我们已经有一个用于获取用户 SOL 余额的工具,但如果我们也想要他们的 Token 余额呢?我们的 Token 帐户工具将允许我们检索给定钱包地址的所有 Token 帐户。将以下代码添加到 src/index.ts
:
server.tool(
"getTokenAccounts",
{
walletAddress: z
.string()
.describe("Solana wallet address to check token accounts for"),
},
async ({ walletAddress }) => {
try {
assertIsAddress(walletAddress);
const accounts = await Promise.all([\
solanaRpc\
.getTokenAccountsByOwner(\
walletAddress,\
{ programId: SPL_PROGRAM_KEYS.TOKEN_PROGRAM },\
{ encoding: "jsonParsed" },\
)\
.send(),\
solanaRpc\
.getTokenAccountsByOwner(\
walletAddress,\
{ programId: SPL_PROGRAM_KEYS.TOKEN_2022_PROGRAM },\
{ encoding: "jsonParsed" },\
)\
.send(),\
]);
const tokenAccounts = accounts.flat();
const tokenAccountDetails = tokenAccounts.flatMap((account) => {
return account.value.map((account) => {
const address = account.pubkey;
const mint = account.account.data.parsed.info.mint;
const amount = account.account.data.parsed.info.tokenAmount.uiAmount;
const decimals =
account.account.data.parsed.info.tokenAmount.decimals;
return { address, mint, amount, decimals };
});
});
// Format data as a markdown table
let markdownTable = "| Token Address | Mint | Amount | Decimals |\n";
markdownTable += "|-------------|------|--------|----------|\n";
tokenAccountDetails
.filter((account) => account.amount !== null)
.filter((account) => account.amount !== 0)
.filter((account) => account.amount !== 1) // removing possible NFTs
.sort((a, b) => b.amount! - a.amount!) // we already removed null and 0 amounts
.forEach((account) => {
markdownTable += `| ${account.address} | ${account.mint} | ${account.amount} | ${account.decimals} |\n`;
});
return {
content: [\
{\
type: "text",\
text: `Found ${tokenAccountDetails.length} token accounts for ${walletAddress}`,\
},\
{\
type: "text",\
text: markdownTable,\
},\
],
};
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `Error while getting balance: ${isSolanaError(error) ? error.message : "Unknown error"}`,\
},\
],
isError: true,
};
}
},
);
从结构上讲,这与 getBalance
工具非常相似。主要的区别是我们使用 getTokenAccountsByOwner
方法来检索给定钱包地址的所有 Token 帐户。我们包括了对旧版 SPL Token 程序和 Token 2022 程序的支持。因为响应可能非常大,所以我们过滤掉余额为 0 或 1(很可能是一个 NFT)的任何帐户,并进行一些处理以将数据的子集格式化为 Markdown 表格。随意尝试不同的响应结构,看看哪种最适合你的用例。
接下来,让我们添加一个用于检查网络状态的工具。此工具将返回当前 epoch、区块高度和槽号。将以下代码添加到 src/index.ts
:
server.tool("networkStatus", {}, async () => {
try {
await solanaRpc.getHealth().send();
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `Network is down`,\
},\
],
};
}
try {
const { epoch, blockHeight, absoluteSlot } = await solanaRpc
.getEpochInfo()
.send();
const status = {
health: "okay",
currentEpoch: epoch.toString(),
blockHeight: blockHeight.toString(),
currentSlot: absoluteSlot.toString(),
};
return {
content: [\
{\
type: "text",\
text: JSON.stringify(status, null, 2),\
},\
],
};
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `Error while getting network status: ${isSolanaError(error) ? error.message : "Unknown error"}`,\
},\
],
isError: true,
};
}
});
在这里,我们实际上在进行两次 RPC 调用。第一个是一个Health Check,以查看网络是否已启动。如果这返回一个错误,我们将返回一条简单的消息说网络已关闭。如果它已启动,我们调用 getEpochInfo
来获取当前的 epoch、区块高度和槽号。我们以 JSON 格式返回此数据。请注意,我们将 bigint 值转换为字符串,以避免 JSON.stringify
出现序列化问题。
接下来,让我们添加一个用于获取交易详情的工具。此工具将采用交易签名作为输入,并返回交易详情。将以下代码添加到 src/index.ts
:
server.tool(
"getTransaction",
{
signature: z.string().describe("Solana transaction signature to look up"),
},
async ({ signature }) => {
try {
assertIsSignature(signature);
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `not a vaid signature: ${signature}`,\
},\
],
isError: true,
};
}
try {
const transaction = await solanaRpc
.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
encoding: "json",
})
.send();
if (!transaction) {
return {
content: [\
{ type: "text", text: `Transaction ${signature} not found` },\
],
isError: true,
};
}
const programIndices = transaction.transaction.message.instructions.map(
(instruction) => instruction.programIdIndex,
);
const programsInvoked = programIndices.map((index) => {
const programId = transaction.transaction.message.accountKeys[index];
return programId.toString();
});
// Format the transaction data for readability
const formattedTx = {
signature,
computeUnits: transaction.meta?.computeUnitsConsumed?.toString(),
logs: transaction.meta?.logMessages,
accountKeys: transaction.transaction.message.accountKeys,
programsInvoked: programsInvoked,
instructions: transaction.transaction.message.instructions,
slot: transaction.slot.toString(),
blockTime: transaction.blockTime
? new Date(Number(transaction.blockTime) * 1000).toISOString()
: null,
fee: transaction.meta?.fee.toString(),
status: transaction.meta?.err ? "Failed" : "Success",
preBalances: transaction.meta?.preBalances.map((balance) =>
balance.toString(),
),
postBalances: transaction.meta?.postBalances.map((balance) =>
balance.toString(),
),
preTokenBalances: transaction.meta?.preTokenBalances,
postTokenBalances: transaction.meta?.postTokenBalances,
};
return {
content: [\
{\
type: "text",\
text: `Transaction ${signature}:\n${JSON.stringify(formattedTx, null, 2)}`,\
},\
],
};
} catch (error) {
return {
content: [\
{\
type: "text",\
text: `Error while getting balance: ${isSolanaError(error) ? error.message : "Unknown error"}`,\
},\
],
isError: true,
};
}
},
);
与我们的余额检查器类似,我们可以使用 Solana Kit 断言来验证我们的参数(在本例中为 assertIsSignature
)。然后,我们调用 getTransaction
方法来检索交易详细信息。我们正在解析数据,并且只返回我们认为对 LLM 分析交易有用的数据的子集。在这种情况下,我们正在提取交易中调用的程序 ID、交易签名、消耗的计算单元、日志、帐户密钥、调用的程序、指令、槽号、区块时间、费用、状态、预余额、后余额以及预/后 Token 余额。与上一个工具一样,我们要确保将 bigint 值转换为字符串以避免序列化问题。
当你在 MCP 上进行试验时,你可以考虑通过利用 IDL 来解析指令数据或添加你认为有用的任何其他附加上下文/映射来稍微扩展一下响应数据。
接下来,让我们添加一个用于获取帐户信息的工具。此工具将采用帐户地址作为输入,并返回帐户信息。将以下代码添加到 src/index.ts
:
server.tool(
"getAccountInfo",
{
walletAddress: z
.string()
.describe("Solana wallet address to check account information for"),
},
async ({ walletAddress }) => {
try {
assertIsAddress(walletAddress);
const accountAddress = address(walletAddress);
const { value: accountInfo } = await solanaRpc
.getAccountInfo(accountAddress)
.send();
if (!accountInfo) {
return {
content: [\
{\
type: "text",\
text: `Account ${walletAddress} not found or has no SOL balance`,\
},\
],
isError: true,
};
}
const info = {
executable: accountInfo.executable,
lamports: accountInfo.lamports.toString(),
owner: accountInfo.owner.toString(),
rentEpoch: accountInfo.rentEpoch.toLocaleString(),
space: accountInfo.data.length,
};
return {
content: [\
{\
type: "text",\
text: JSON.stringify(info, null, 2),\
},\
],
};
} catch (error) {
console.error("Error fetching account info:", error);
return {
content: [\
{\
type: "text",\
text: `Error while getting account info: ${isSolanaError(error) ? error.message : "Unknown error"}`,\
},\
],
isError: true,
};
}
},
);
这些模式现在应该很熟悉了。我们正在使用 getAccountInfo
方法来检索帐户信息。
MCP 中的资源为服务器提供了一种暴露结构化数据的方式,这些数据可以被 LLM 引用和使用。与在被调用时执行操作的工具不同,资源更像是“只读端点”,以标准化格式暴露数据。它们由 URI(统一资源标识符)标识,URI 遵循诸如 file://、https:// 或自定义方案(如我们的示例中的 solana://)之类的方案。
这是一个如何将文档资源拉入你的服务器的综合示例。这是我们的 Solana 交易优化文档 内容的简写版本:
server.resource(
"transaction-optimization",
"solana://docs/transaction-optimization",
async (uri) => {
const optimizationGuide = {
title: "Solana Transaction Optimization Strategies",
strategies: {
priority_fees: {
description: "Increase transaction priority in validator queues",
implementation:
"Use ComputeBudgetProgram.setComputeUnitPrice({microLamports})",
best_practice:
"Use QN Priority Fee API to determine optimal fee based on network conditions",
},
compute_units: {
description:
"Optimize compute unit usage to prevent transaction drops",
current_limits: {
per_block: "48 million",
per_account_per_block: "12 million",
per_transaction: "1.4 million",
transaction_default: "200,000",
},
implementation:
"Use ComputeBudgetProgram.setComputeUnitLimit({units}) after simulation",
},
transaction_assembly: {
steps: [\
"Create transaction with instructions",\
"Fetch and add priority fees",\
"Simulate transaction to determine compute usage",\
"Set compute limit based on simulation",\
"Add recent blockhash",\
"Sign and send",\
],
},
jito_bundles: {
description: "Bundle multiple transactions for atomic execution",
requires: "SOL transfer to Jito Tip Account",
},
confirmation: {
description: "Poll transaction status to ensure it landed",
method: "Use getSignatureStatuses and implement retry logic",
},
},
moreInfo: "https://www.quicknode.com/docs/solana/transactions",
};
return {
contents: [\
{\
uri: uri.href,\
text: JSON.stringify(optimizationGuide, null, 2),\
},\
],
};
},
);
此资源提供了有关 Solana 交易优化策略的结构化概述。LLM 可以使用此资源来回答有关交易优化的问题,而无需调用工具或 API。URI 方案 (solana://) 表示这是一种自定义资源类型,并且内容以结构化格式返回。
MCP 中的提示是预定义的模板消息,可帮助指导 AI 与你的工具和资源的交互。与工具(执行代码)或资源(提供数据)不同,提示是构造 LLM 应如何处理特定任务的配方。
让我们添加一些有用的提示来指导 LLM 使用我们的工具。将以下代码添加到 src/index.ts
:
server.prompt(
"analyze-wallet",
{ walletAddress: z.string() },
({ walletAddress }) => ({
description:
"Analyze a Solana wallet address and provide a summary of its balances and activity",
messages: [\
{\
role: "user",\
content: {\
type: "text",\
text: `Please analyze this Solana wallet address: ${walletAddress}\
\
1. What is the SOL balance of this wallet?\
2. What token balances does this wallet hold?\
3. Provide a summary of recent activity if possible.`,\
},\
},\
],
}),
);
server.prompt(
"explain-transaction",
{ signature: z.string() },
({ signature }) => ({
description: "Analyze and explain a Solana transaction in simple terms",
messages: [\
{\
role: "user",\
content: {\
type: "text",\
text: `Please analyze this Solana transaction signature: ${signature}\
\
1. Was this transaction successful?\
2. What type of transaction is this? (e.g., token transfer, swap, NFT mint)\
3. What accounts were involved?\
4. Explain what happened in simple terms.`,\
},\
},\
],
}),
);
每个提示包括:
当用户在 Claude Desktop 中选择提示时,他们会看到一个表单来填写参数。提交后,Claude 会收到格式化的消息模板并做出相应的响应。提示对于以下方面特别有用:
现在我们已经添加了我们所有的工具、资源和提示,让我们再次构建服务器:
npm run build
既然我们已经扩展了服务器的功能,让我们使用 Claude Desktop 测试新功能。在配置更改后,请确保关闭 Claude Desktop 并重新打开它以重新启动服务器。打开 Claude Desktop 后,你应该会看到你的工具菜单现在有所有 5 个可用工具:
随意进行一些测试——你可以尝试以下操作:
你还应该注意到一个 🔌“从 MCP 附加” 按钮。点击它,你应该会看到你的资源和提示可供使用:
如果你点击其中一个提示,你应该会看到一个表单,允许你填写参数(例如,钱包地址或 txid):
填写参数并点击“提交”后,Claude 将使用提示生成消息(利用必要的工具)并做出相应的响应。试一下。
最后,我们可以测试我们的资源。你可以通过在提示中添加 transaction-optimization
,然后询问 Claude 一些与之相关的内容来做到这一点。试一下:
干得好!
你已成功构建了一个 Solana MCP 服务器,该服务器通过区块链功能增强了 LLM 的功能。此集成允许 Claude 直接与 Solana 区块链交互——检查余额、检索 Token 信息、检查交易等等。 通过将 AI 助手的自然语言理解与 MCP 服务器的数据访问功能相结合,你创建了一个用于区块链交互的强大工具。无论你是开发人员、交易员,还是只是对 Solana 好奇,此集成都可以使区块链数据更易于访问和理解。 MCP 框架灵活且可扩展,因此你可以随着需求的演变继续使用其他功能来增强服务器。可能性几乎是无限的——从简单的数据查询到复杂的分析、交易构建(具有适当的安全措施)和代理程序集。 我们希望本指南能帮助你开始使用 MCP 和 Solana 集成。想扩展你已经构建的内容吗?以下是一些用于持续开发的思路:
既然你已经有了一个功能正常的 MCP 服务器,以下是一些用于进一步增强功能的思路- 本指南的代码
如果你有任何反馈或对新主题的请求,请告诉我们。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/ai/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!