news 2026/9/15 12:41:59

todos-server 全功能 MCP 参考服务器实战:从 8 个工具到双协议双传输的完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
todos-server 全功能 MCP 参考服务器实战:从 8 个工具到双协议双传输的完整实现

todos-server 全功能 MCP 参考服务器实战:从 8 个工具到双协议双传输的完整实现

【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk

todos-server 是 TypeScript SDK(Model Context Protocol 官方 SDK)仓库中作为**参考服务器(reference server)**存在的示例包:一个麻雀虽小、五脏俱全的项目待办看板,让 MCP 的每一项服务端能力都有真实用途——工具负责改状态、资源负责暴露状态、提示词负责播种对话、采样借用宿主的大模型、elicitation 向用户提问、进度与日志贯穿执行过程、按资源订阅通报每一次变更。它同时承担2026-07-28 与 2025-11-25 两个协议版本的按连接协商,以及stdio 与 Streamable HTTP 两种传输。读完本文,你将掌握:如何运行并连接这个参考服务器、它如何用一份代码同时服务两个协议时代、多轮input_required与 HMAC 签名requestState的落地写法,以及每一项服务端 MCP 能力的源码级实现位置。

一、定位:MCP 服务器世界的 "polls app"

原文档(examples/todos-server/README.md)用一个非常贴切的比喻定义了它的定位:它是 MCP 服务器的 "polls app"——小到可以一口气读完,又真实到没有任何内容是刻意拼凑的。它是参考宿主cli-client开箱即连的标准负载,你可以把它当作一份"服务端 MCP 能力全清单"来阅读。

与仓库中其他"单特性故事"示例(如 examples/elicitation、examples/sampling)不同,todos-server 刻意设计为server-only包(见 package.json 中example.excludedshapeExempt字段的注释),它没有自己的client.ts,端到端覆盖完全由 cli-client 的脚本化 e2e 在 CI 中驱动,横跨 stdio + HTTP 两种传输、两个协议时代。

整个包只有两个文件,职责划分极为清晰:

server.ts transport entry: serveStdio by default, createMcpHandler + node adapter behind --http todos.ts the application: state, tools, resources, prompts, subscriptions — every feature above

二、运行它:一条命令,两种传输

从仓库根目录首次运行需先pnpm install && pnpm build:all,之后:

# stdio — 适用于把服务器作为子进程拉起的宿主 pnpm --filter @mcp-examples/todos-server start # Streamable HTTP — 适用于远程式连接(默认端口 3000;--port 可改) pnpm --filter @mcp-examples/todos-server start:http

两个命令对应 package.json 中的两条脚本:starttsx server.tsstart:httptsx server.ts --http。stdio 模式下服务器在 stdin/stdout 上说话,自己的诊断信息走 stderr;HTTP 模式下它通过createMcpHandler的按请求(per-request)模型在http://127.0.0.1:3000/mcp提供服务。

命令行参数由共享工具 parseExampleArgs 解析:--http切换到 HTTP 传输,--port <N>覆盖端口(回退到$PORT环境变量,再回退到默认 3000)。

关键设计:服务器上没有任何"时代开关"。从 todos.ts 和 server.ts 可以看到,serveStdiocreateMcpHandler在握手阶段自动检测每条连接的协议版本并据此固定(pin)实例——所以一个 2025 时代的客户端和一个 2026 时代的客户端可以同时跟同一个进程对话(HTTP 下甚至是并发的)。其底层机制可参见 serveStdio.ts 的文档注释:stdio 入口对连接的开场交换只分类一次,用同一个工厂按客户端开启的时代构建一个实例并为其终身固定,此后不再做任何逐消息时代分类;initialize握手(或任何无信封声明的消息)固定为 2025 时代会话,携带有效_meta信封的请求固定为现代实例,server/discover探测则先乐观构建现代实例、不立即固定,允许客户端回退到initialize

三、把参考宿主连上来:两条终端、一条配置

3.1 HTTP 对连(完整演示)

# 终端 A — 用 Streamable HTTP 提供参考服务器(端口 3000) pnpm --filter @mcp-examples/todos-server start:http # 终端 B — 把宿主连上去(加上 provider key 可接真实模型) pnpm --filter @mcp-examples/cli-client start -- --server http://127.0.0.1:3000/mcp

客户端的状态行会展示协商结果connected to "todos" (2026-07-28, 8 tools, …);若在终端 B 追加--legacy强制 2025 时代握手,同一服务器上会看到(2025-11-25, …),运行的是每个功能的 legacy 分支。要精确锁死某个协议版本可用--protocol-version(如--protocol-version 2025-06-18),协商不到即连接失败。

如果想快速瞄一眼,甚至不需要 HTTP 步骤——cli-client不带任何参数运行时会自动通过 stdio 把 todos-server 作为子进程拉起。

3.2 任何 mcpServers 风格的宿主

{ "mcpServers": { "todos": { "command": "npx", "args": ["-y", "tsx", "/absolute/path/to/examples/todos-server/server.ts"] } } }

注意路径必须是绝对路径,因为相对路径是相对于运行 CLI 的目录解析的(见 examples/cli-client/README.md 中对mcpServers配置的说明)。

3.3 一次坐下来的全功能巡游

cli-client 的 README 提供了一条"一坐到底"的巡游路线,几乎覆盖 todos-server 的每一项能力:

brainstorm some tasks ← elicitation 表单(主题 + 数量)+ 需批准的采样 prioritize my open tasks ← 采样:执行前先请你批准请求 /todos:plan-my-day focus=ops ← 以斜杠命令形式调用 MCP 提示词(支持 Tab 补全) @todos:todos://board what's next? ← 把资源作为上下文挂进来 /watch @todos:todos://board ← 订阅:看板一变就出现通知 do all my tasks ← 逐任务进度 + 日志通知实时流出 (Ctrl-C mid-run) ← 取消:工具提前停止,模型被告知 clear my completed tasks ← elicitation 确认的批量删除 /help /servers /tools /resources /prompts /roots

四、功能总览:每个服务端特性住在哪里

原文档用一张表格完整交代了"什么演示什么",完整继承如下:

服务端特性所在位置说明
Toolsadd_taskadd_taskslist_taskscomplete_task纯 CRUD;add_task还按outputSchema返回structuredContent
Samplingprioritizebrainstorm_tasks服务器借用宿主的模型;宿主会先展示请求供批准
Elicitation(表单)clear_donebrainstorm_tasksschema 驱动的表单;accept / decline / cancel 全部处理
多轮 input_requiredbrainstorm_tasks主题+数量表单 → 可选自定义数量轮 → 采样轮;状态挂在requestState上作为按步骤区分的联合类型(step-discriminated union),经createRequestStateCodecHMAC 签名
进度 + 取消work_through_tasksadd_tasks逐任务节奏化进度通知;work_through_tasks在任务之间检查ctx.mcpReq.signal,宿主取消时提前停止
日志每个变更工具,经ctx.mcpReq.log2025 连接上遵从logging/setLevel,2026-07-28 上遵从按请求的日志级别_meta选择加入
Resourcestodos://boardtodos://tasks/{id}一个具体资源 + 一个带补全回调的ResourceTemplate
订阅看板资源2025 时代客户端走resources/subscribe/unsubscribe;2026-07-28 走subscriptions/listen路由;每次变更都会通知
list_changed每次变更资源列表 + 资源更新通知,在 stdio 与按请求 HTTP 上都正确投递
Prompts + completionsplan-my-dayseed-boardcompletable()参数值(项目名、主题)接到completion/complete

两个协议时代如何差异地承载交互

两个协议时代在"交互对话如何在线缆上传输"上本质不同:2025 时代连接上,线缆承载的是推送式elicitation/create/sampling/createMessage请求;2026-07-28 上,服务器返回input_required结果、客户端携带答案重试该调用。而 todos-server 的三个交互式工具(brainstorm_tasksclear_doneprioritize只用input_required风格写一遍——在 2025 时代连接上,SDK 默认开启的 legacy shim 为它们执行推送式的往返,所以任何 handler 里都没有时代分支

这个"写一次、双时代运行"的关键就是 SDK 的 legacyInputRequiredShim.ts:当 handler 在 2025 时代请求上返回 input-required 结果时,shim 把每个内嵌请求作为真实的服务器→客户端请求发出(elicitation/createsampling/createMessageroots/list,并盖上源请求 id 以便流关联),收集inputResponses后重新进入 handler,直到它返回最终结果或轮数上限(默认 8 轮)耗尽。语义与现代客户端驱动完全对齐:每轮 REPLACED 的inputResponses、字节精确的requestState回显(每轮由配置的钩子重新验证)、仅 requestState 轮的节奏化等待——因此 handler 无法察觉自己由哪个时代履约。

唯一的服务模式注意事项:通过HTTP + 2025 时代客户端连接时,createMcpHandler默认的无状态(stateless)姿态没有推送式服务器→客户端请求的返回路径,所以采样/elicitation 工具在该腿(leg)上会干净地拒绝(stdio 不受影响;2026-07-28 的 HTTP 不受影响)。这与 examples/sampling/README.md 中提到的同一 caveat 一致,cli-client 的 CI 脚本也因此在 legacy HTTP 腿上跳过采样/elicitation 步骤。如需 2025 时代的会话式 HTTP 服务,可依据 createMcpHandler.ts 中isLegacyRequest谓词文档给出的模式,在用户层路由到既有的会话式传输实现。

五、从源码看每项能力的落地

5.1 工具:CRUD、批量与结构化输出

四个纯 CRUD 工具都在 todos.ts 的buildServer中注册。add_task值得细看的是它同时返回contentstructuredContent

server.registerTool( 'add_task', { description: 'Add a task to the board', inputSchema: z.object({ title: z.string().describe('What needs doing'), project: z.string().optional().describe('Project bucket, e.g. "ops"'), priority: z.enum(['high', 'medium', 'low']).optional(), due: z.string().optional().describe('Free-form due date, e.g. "Friday"'), notes: z.string().optional() }), outputSchema: z.object({ id: z.string(), title: z.string(), status: z.enum(['open', 'done']) }) }, async ({ title, project, priority, due, notes }, ctx) => { const task = addTask({ title, project: project ?? 'inbox', priority, due, notes }); await announceBoardChange(); await logInfo(ctx, `added ${task.id}: ${task.title}`); return { content: [{ type: 'text', text: `Added ${task.id}: ${describeTask(task)}` }], structuredContent: { id: task.id, title: task.title, status: task.status } }; } );

add_tasks则演示了批量 + 进度:每插入一条任务假装耗时 100ms(await new Promise(resolve => setTimeout(resolve, 100))),让宿主有在途进度可渲染;complete_task支持按 id 或标题子串匹配;list_tasks支持status(open/done/all)与project过滤。每个变更工具都调用announceBoardChange()logInfo(),把"改状态"和"通报"绑定在一起。

5.2 资源:具体资源 + 带补全的模板

todos://board是具体资源,把整个看板渲染为 markdown(mimeType: 'text/markdown'):

server.registerResource( 'board', 'todos://board', { description: 'The whole todo board as markdown', mimeType: 'text/markdown' }, async uri => ({ contents: [{ uri: uri.href, mimeType: 'text/markdown', text: renderBoard() }] }) );

todos://tasks/{id}是一个ResourceTemplate,同时给出了列表回调list,用于枚举现有任务资源)和补全回调complete: { id: value => [...tasks.keys()].filter(id => id.startsWith(value)) },让客户端可以对任务 id 做参数补全):

server.registerResource( 'task', new ResourceTemplate('todos://tasks/{id}', { list: async () => ({ resources: [...tasks.values()].map(task => ({ uri: `todos://tasks/${task.id}`, name: task.title, mimeType: 'text/markdown' })) }), complete: { id: value => [...tasks.keys()].filter(id => id.startsWith(value)) } }), { description: 'A single task by id', mimeType: 'text/markdown' }, async (uri, variables) => { const task = tasks.get(String(variables.id)); return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: task ? describeTask(task) : `No task with id ${String(variables.id)}` }] }; } );

5.3 提示词与补全

两个提示词都把参数声明为completable()——这正是 MCPcompletion/complete的数据源:

  • seed-boardtheme参数从一份固定主题列表("space-station maintenance"、"wizard tower chores"……)按前缀过滤补全;
  • plan-my-dayfocus参数从当前看板的实际项目列表(projects())补全,返回值是一段多角色消息序列(user → assistant → user),用于播种一段围绕当前看板的规划对话。

在 cli-client 里,提示词以/todos:plan-my-day focus=ops这样的斜杠命令调用,Tab 会补全命令、提示词名、@server:uri提及以及提示词参数值——后者正是走 MCPcompletion/complete

5.4 采样:借用宿主的模型

prioritize工具把当前所有打开的任务标题发给宿主模型,请它按重要度排序("Reply with one task title per line, most important first"),然后按排名把任务分成 high / medium / low 三档(priorityForRank用三等分法)。brainstorm_tasks的采样请求由buildBrainstormSampling构造:给定主题与数量,请求模型逐行输出创意任务,maxTokens随数量缩放(Math.min(200 + wanted * 40, 1500))。

在宿主侧,采样请求会先完整展示给你批准,批准后才通过驱动聊天的同一个LLMProvider.generate()路由出去——参考宿主的设计原则是"批准显式且失败即关闭(fail closed)":展示完整请求而非预览,并且无论服务器要求多少都封顶maxTokens(见 examples/cli-client/README.md 的 Design notes)。

5.5 Elicitation:schema 驱动的表单

clear_done用一个布尔确认表单请求删除授权:

const CLEAR_CONFIRM_SCHEMA: ElicitRequestFormParams['requestedSchema'] = { type: 'object', properties: { confirm: { type: 'boolean', title: 'Delete all completed tasks?', description: 'This cannot be undone.' } }, required: ['confirm'] };

单轮input_required的写法是:首次调用没有inputResponses,返回问题;重调用携带答案。答案的 accept / decline / cancel 三种走向都被elicitAction识别——confirm !== true时报告"用户回答了什么"并停止,绝不追问第二次:

const response = ctx.mcpReq.inputResponses?.['confirmation']; if (response === undefined) { return inputRequired({ inputRequests: { confirmation: inputRequired.elicit({ message, requestedSchema: CLEAR_CONFIRM_SCHEMA }) } }); } const action = elicitAction(response); const confirmation = acceptedContent<{ confirm?: boolean }>(ctx.mcpReq.inputResponses, 'confirmation'); if (confirmation?.confirm !== true) { return { content: [{ type: 'text', text: `Nothing deleted (user answered: ${action}).` }] }; }

5.6 多轮 input_required:状态机 + HMAC 签名的 requestState

brainstorm_tasks是本文档中最值得研读的部分——它把整个对话实现为一个显式的多轮input_required,共三至四轮:主题+数量表单 →(可选)自定义数量表单 → 采样轮 → 落盘。状态是一个按步骤区分的联合类型

type BrainstormState = | { step: 'awaiting-count' } | { step: 'awaiting-custom-count'; topic: string } | { step: 'awaiting-ideas'; topic: string; count: number };

handler 按state.step分派(而不是按"来了哪个inputResponses键"),所以每一轮都知道该读哪个答案、哪些数据在作用域内。requestStatecreateRequestStateCodec铸造(mint)并经stateCodec.verify验证——客户端无法伪造或篡改携带的 step/theme/count。其实现见 requestStateCodec.ts:

  • 线缆格式为"v1." b64url({"p":<payload>,"exp":<unixSeconds>,"b":<bindTag>?}) "." b64url(mac),MAC 覆盖版本前缀 + body,域分离的 bind 标签截断到 128 位;
  • 密钥要求≥ 32 字节RangeError于构造时抛出),ttlSeconds默认 600 秒(过期即拒绝),可选bind回调把状态绑定到认证主体/方法;
  • 它是签名而非加密:客户端可以 base64url 解码看到明文 payload,所以不要把机密放进 payload(注释明确建议需要机密性时用 AEAD 构造);
  • 验证失败即关闭(fail-closed)且常量时间(body MAC 用 WebCryptosubtle.verify,bind 标签用定长 XOR 累加比较),失败原因只暴露'malformed' / 'mac' / 'expired' / 'bind'这种不透明码。

关键点:verify由 seam 在 handler进入之前运行(篡改以冻结的-32602拒绝),handler 通过类型化的ctx.mcpReq.requestState<BrainstormState>()访问器读取已解码的 payload,无需二次解码。todos.ts 中的工厂构造如下:

const stateCodec = createRequestStateCodec<BrainstormState>({ key: process.env.REQUEST_STATE_SECRET ?? crypto.getRandomValues(new Uint8Array(32)) });

即密钥来自环境变量,未设置时退化为每进程随机密钥——对"单进程服务整个流程"的演示足够(因为同 key 必须对所有可能收到回显requestState的服务器实例可用)。代码中stateCodec.mint(payload, ctx)ctx.mcpReq.requestState<T>()构成类型化的编码/读取配对。

5.7 进度、取消与日志

  • 进度reportProgressctx.mcpReq._meta?.progressToken读取令牌,然后发notifications/progress(携带progressToken, progress, total, message)。add_taskswork_through_tasks都用它做节奏化进度播报。
  • 取消work_through_tasks在任务之间检查ctx.mcpReq.signal.aborted——宿主通过RequestOptions.signal中止调用(SDK 会发notifications/cancelled),服务器据此提前停止并报告"已处理 X / N 个任务"。cli-client 侧对应 Ctrl-C 中断运行中的工具调用。
  • 日志logInfoctx.mcpReq.log('info', text, 'todos'),请求绑定,在 2025 连接上遵从客户端的logging/setLevel阈值,在 2026-07-28 连接上遵从按请求的logLevel选择加入。work_through_tasks还内置了一份 8 条的打趣文案池("applying percussive maintenance"、"consulting the rubber duck for a second opinion"……),让日志流有真实感。

5.8 订阅与 list_changed

订阅逻辑按协议时代分两套,但都由announceBoardChange统一收口:

  • 2025 时代客户端调用resources/subscribe/resources/unsubscribe,服务器在subscribedUris集合里跟踪订阅者,更新只发给订阅者;
  • 2026-07-28 客户端使用subscriptions/listen过滤器,由服务入口把同一通知路由到打开的监听流;
  • 每次变更调用server.sendResourceListChanged(),并(视时代与传输)发送sendResourceUpdated({ uri: 'todos://board' })

announceBoardChange中还体现了一个传输相关的设计决策:按请求 HTTP 服务没有向下的推送通道,跨请求事件(看板变化)改由 handler 的 notifier 发布——这正是 server.ts 中 HTTP 分支要做的事:

const handler = createMcpHandler(buildServer); onBoardChanged(() => handler.notify.resourcesChanged()); onBoardUpdated(uri => handler.notify.resourceUpdated(uri));

stdio 分支则保持未设置,由固定实例自己的通知由服务入口路由。buildServer也据此接受reqCtx: McpRequestContext——在announceBoardChange里用reqCtx.era === 'modern'判断走哪条投递路径。

六、传输层:server.ts 的双入口骨架

server.ts 是标准的双传输骨架,与其他示例一致:

const { transport, port } = parseExampleArgs(); if (transport === 'stdio') { void serveStdio(buildServer); console.error('[todos] serving over stdio'); } else { const handler = createMcpHandler(buildServer); onBoardChanged(() => handler.notify.resourcesChanged()); onBoardUpdated(uri => handler.notify.resourceUpdated(uri)); const app = createMcpHonoApp(); app.all('/mcp', c => handler.fetch(c.req.raw)); serve({ fetch: app.fetch, port, hostname: '127.0.0.1' }, () => { console.error(`[todos] listening on http://127.0.0.1:${port}/mcp`); }); }

要点:

  • 同一个工厂buildServer供两个入口共用serveStdio(来自@modelcontextprotocol/server/stdio)与createMcpHandler(来自@modelcontextprotocol/server)都按服务单元调用该工厂:stdio 每个连接一次,HTTP 每个请求一次。这正是两个协议时代"永不漂移"的结构保证(见 createMcpHandler.ts 文档:"Use ONE factory for both legs")。
  • 工厂接收McpRequestContext,内含era: 'legacy' | 'modern'等字段(详见 createMcpHandler.ts 的McpRequestContext接口定义),实例据此构建并固定时代。
  • HTTP 腿通过 Hono 框架承载:createMcpHonoApp()默认绑定 localhost 的 host/origin 校验,app.all('/mcp', c => handler.fetch(c.req.raw))把 web-standard fetch 面接到 Hono 上,再用@hono/node-serverserve监听127.0.0.1:PORThandler.notify是类型化的发布侧门面,把变更事件发布到每个已打开且选择接收该通知类型的订阅流(无订阅打开时安全空操作)。

从 createMcpHandler.ts 的源码还可以看到入口对内层路由的完整处理:对每个入站 HTTP 请求做恰好一次分类(body-primary),现代路径(带_meta信封)用新鲜实例 + 单交换 per-request 传输服务;无信封声明的请求(含initialize、GET/DELETE 会话操作、2025 通知 POST)按默认legacy: 'stateless'姿态逐请求服务,可选的legacy: 'reject'则打造现代-only 严格端点。入口本身不做 Origin/Host 校验(由中间件负责,Hono 工厂默认代劳)也不做 token 校验(authInfo严格透传)。

七、配置:两个环境变量

原文档的配置表完整继承如下:

环境变量作用
REQUEST_STATE_SECRET签名requestState的 HMAC 密钥(≥ 32 字节)。未设置时服务器生成每进程随机密钥——只要单个进程服务整个流程就完全够用。
PORT--port未传入时的 HTTP 端口(默认 3000)。

第一个变量的边界值得强调:createRequestStateCodec在构造时要求密钥至少 32 字节,且同一个密钥必须对所有可能收到回显requestState的服务器实例可用——所以多进程部署必须显式配置同一密钥,否则一个进程铸的 token 在另一个进程验证时会因 MAC 不符而被拒。

八、布局与测试覆盖

server.ts transport entry: serveStdio by default, createMcpHandler + node adapter behind --http todos.ts the application: state, tools, resources, prompts, subscriptions — every feature above

本包刻意 server-only,其端到端覆盖来自 examples/cli-client/README.md 所述的脚本化 e2e:client.ts(CI 入口)用ScriptedProvider重放一段脚本化对话,逐步断言循环、命名空间、资源挂载、提示词角色处理、采样批准、多轮 elicitation + 签名requestState流程、补全、取消、进度与日志确实完成了往返——覆盖 stdio 与 Streamable HTTP、两个协议时代(进度/日志/订阅断言跑在时序确定的 stdio 腿上)。pnpm run:examples在 CI 中执行它。

结语

todos-server 的价值不在于功能多,而在于它把 MCP 服务端的每一项能力都用"真实业务"串了起来:CRUD 工具、资源与模板、提示词与补全、借用宿主模型的采样、schema 驱动的 elicitation、带 HMAC 签名状态的多轮 input_required、进度/取消/日志、按资源订阅与 list_changed——并且全部只写一遍,同时服务 2025-11-25 与 2026-07-28 两个协议时代、stdio 与 Streamable HTTP 两种传输。对想学习或复制 MCP 服务端最佳实践的开发者来说,它是一份可以通读的参考实现:先读 todos.ts 看应用层每项能力怎么注册,再读 server.ts 看双传输骨架怎么搭,最后对照 cli-client 看宿主侧如何消费这一切。

【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 12:40:50

API越权漏洞自动化检测实战:Hadrian+Vespasian+crAPI工具链解析

API越权漏洞在OWASP API Security Top 10 2023里占了两个席位——BOLA&#xff08;对象级授权缺失&#xff09;排第一&#xff0c;BFLA&#xff08;功能级授权缺失&#xff09;排第五。名字听着很学术&#xff0c;翻译成大白话就是水平越权和垂直越权。手动测越权是安全测试里最…

作者头像 李华
网站建设 2026/9/15 12:40:43

程序员考公如何备考:6 个月迭代式上岸的完整计划

程序员考公如何备考&#xff1a;6 个月迭代式上岸的完整计划 【免费下载链接】developer2gwy 公务员从入门到上岸&#xff0c;最佳程序员公考实践教程 项目地址: https://gitcode.com/GitHub_Trending/de/developer2gwy 深夜在工位上收到优化通知&#xff0c;或者只是看…

作者头像 李华
网站建设 2026/9/15 12:38:05

WRF后处理实战:nc变量提取、物理量计算与可视化避坑指南

1. 这不是教程&#xff0c;是我在气象建模组熬了三年夜班后整理的WRF后处理实操手记WRF模型跑完输出那堆nc文件&#xff0c;很多人第一反应是——“终于跑完了&#xff1f;可以交差了&#xff1f;”然后双击打开netCDF文件&#xff0c;看到满屏的time、south_north、west_east维…

作者头像 李华
网站建设 2026/9/15 12:38:03

Craft Agents自动更新系统解析:auto-update.ts工作原理

Craft Agents自动更新系统解析&#xff1a;auto-update.ts工作原理 【免费下载链接】craft-agents-oss 项目地址: https://gitcode.com/GitHub_Trending/cr/craft-agents-oss Craft Agents 是一款面向 AI 智能体协作的开源桌面应用&#xff0c;它的自动更新系统让用户无…

作者头像 李华
网站建设 2026/9/15 12:38:00

河南省30米DEM数据处理实战:GDAL拼接、裁剪与坡度计算

简介&#xff1a;河南省30米分辨率DEM数字高程模型&#xff0c;适合GIS、遥感或地理测绘领域的从业者与学生使用。数据基于ASTER GDEM V3拼接而成&#xff0c;采用GeoTiff格式与WGS84坐标系&#xff0c;覆盖河南全省范围&#xff0c;可直接用于地形渲染、坡度坡向提取、流域分析…

作者头像 李华