【MCP实战】从0写一个本地工具服务器:文件搜索、SQLite查询与安全边界
1. 从零写一个本地 MCP 工具服务器文件搜索与 SQLite 查询到底解决什么问题MCPModel Context Protocol本地工具服务器简单说就是让 AI 客户端通过标准协议调用你自己写的本地能力。它适合谁适合那些不满足于天气查询 demo、想把 AI 真正接进自己开发环境的工程师。我这次要做的是一个用 TypeScript 写的 stdio 传输 MCP 服务提供两个工具search_files在允许的项目目录里搜文本query_tasks从本地 SQLite 只读查询任务记录。为什么不用现成的因为现成工具要么权限太大要么返回结构不可控。自己写的好处是边界清晰根目录由服务端配置决定不由模型决定文件搜索限制扩展名、大小和返回数量数据库只读打开不接受任意 SQL。这些才是 MCP 工具真正值得花时间的地方。先确认环境。Node.js 和 npm 版本node --version npm --version本文环境示例v24.15.0、11.12.1。SDK v2 拆了包只写服务端装这些就够npm init -y npm install modelcontextprotocol/server zod npm install -D typescript types/node如果你看到旧文章仍然从modelcontextprotocol/sdk/server/mcp.js导入不要两套示例混在一起。先看项目装的是 SDK v1 还是 v2再决定导入路径。package.json可以整理为{ name: mcp-local-lab, version: 1.0.0, type: module, private: true, scripts: { build: tsc -p tsconfig.json, dev: npm run build node build/index.js, init-db: node scripts/init-db.mjs }, dependencies: { modelcontextprotocol/server: ^2.0.0, zod: ^4.0.0 }, devDependencies: { types/node: ^24.0.0, typescript: ^5.9.0 } }版本号不用抄到最后一位。关键是SDK v2 使用拆分后的服务端包项目可用 ESMNode.js 版本满足当前 SDK 要求zod 的主版本与 SDK 示例一致。tsconfig.json{ compilerOptions: { target: ES2023, module: NodeNext, moduleResolution: NodeNext, rootDir: src, outDir: build, strict: true, noUncheckedIndexedAccess: true, esModuleInterop: true, skipLibCheck: true }, include: [src/**/*.ts] }我比较建议打开strict和noUncheckedIndexedAccess。MCP 工具本质上在处理外部参数类型检查越严越容易在“理论上有值”的地方得到undefined。编译器提前指出问题总比客户端调用后返回内部错误更省时间。这一节的核心检索词是“MCP 本地工具服务器 TypeScript 实现”。你只要记住三件事stdio 是传输方式工具是能力单元安全边界是服务端责任。接下来所有代码都围绕这三点展开。2. TaoToken 前置本地 MCP 服务如何接入模型调用链路写本地工具服务器之前得先想清楚模型从哪里来。MCP 服务本身只负责暴露工具真正发起对话、决定调用哪个工具的是 AI 客户端背后的模型。如果你用的是云端模型就需要一个稳定的 API 入口。我这边用 TaoToken 作为模型调用入口官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。为什么先讲这个因为很多人写完 MCP 服务卡在“客户端连不上模型”这一步误以为是工具注册错了。实际上工具注册和模型接入是两条链路。工具注册走 stdio模型调用走 HTTP API。两条链路都要通整个流程才跑得起来。TaoToken 在这里的角色是提供兼容常见协议格式的模型 API。你可以在模型对话页面直接验证模型是否可用地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。验证通过后再把它配到支持 MCP 的客户端里。如果你打算长期做编码类 Agent可以了解 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它更适合持续性的编码任务而不是一次性验证。API Key 在控制台创建地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 密钥管理页面是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。这里要强调一个概念Base URL、API Key、Model ID 是三件套缺一不可。Base URL 决定请求发到哪里API Key 决定身份Model ID 决定用哪个模型。很多 401 报错不是 Key 错了而是 Base URL 写成了别的地址或者 Model ID 拼错。对于 Claude Code 这类工具接入时同样需要这三件套。相关说明在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。注意MCP 服务和模型接入是分开配置的MCP 服务写在客户端的 MCP 配置里模型接入写在客户端的模型配置里。我试过把这两件事混在一起排查结果浪费了很多时间。后来分开看就清楚了工具没出现查 MCP 配置模型不回复查 API 配置。两条链路各自独立验证问题定位快很多。这一节不是让你先注册再写代码而是让你明白整个调用链路。本地 MCP 服务负责“能做什么”TaoToken 负责“谁来决策”。两者配合才是一个完整的本地 AI 工具环境。3. 可复制配置文件搜索与 SQLite 查询的完整实现这一节给出可直接复制的代码。先定义文件搜索结果结构src/file-search.tsimport { promises as fs } from node:fs; import path from node:path; export interface SearchMatch { file: string; line: number; preview: string; } export interface SearchOptions { keyword: string; extensions: string[]; limit: number; }返回结果只保留相对路径、行号、截断后的当前行。不返回绝对路径是为了减少不必要的本机信息暴露不返回整个文件是为了控制结果大小。路径校验不能只用startsWith。下面这种写法看起来能判断子路径candidate.startsWith(root);但如果根目录是D:\code\app另一个目录是D:\code\app-backup字符串同样以D:\code\app开头。更稳妥的判断方式是使用path.relativefunction isInside(root: string, candidate: string): boolean { const relative path.relative(root, candidate); return ( relative || (!relative.startsWith(..) !path.isAbsolute(relative)) ); }除此之外还要注意符号链接。目录表面上在项目中但符号链接可能指向项目外。真正读取前应尽量对根目录和目标文件执行realpath再做一次范围判断。递归搜索实现const ignoredDirectories new Set([ .git, node_modules, build, dist, coverage ]); async function* walk(root: string): AsyncGeneratorstring { const entries await fs.readdir(root, { withFileTypes: true }); for (const entry of entries) { if (entry.isSymbolicLink()) { continue; } const fullPath path.join(root, entry.name); if (entry.isDirectory()) { if (!ignoredDirectories.has(entry.name)) { yield* walk(fullPath); } continue; } if (entry.isFile()) { yield fullPath; } } }这里直接跳过符号链接。这不是所有场景的唯一答案但对本地只读搜索工具来说它比“跟随链接后再判断”更简单也更容易解释。继续完成搜索函数export async function searchFiles( projectRoot: string, options: SearchOptions, maxFileBytes: number ): PromiseSearchMatch[] { const rootRealPath await fs.realpath(projectRoot); const normalizedExtensions new Set( options.extensions.map((item) item.startsWith(.) ? item.toLowerCase() : .${item.toLowerCase()} ) ); const keyword options.keyword.toLowerCase(); const matches: SearchMatch[] []; for await (const filePath of walk(rootRealPath)) { if (matches.length options.limit) { break; } const extension path.extname(filePath).toLowerCase(); if (!normalizedExtensions.has(extension)) { continue; } const stat await fs.stat(filePath); if (stat.size maxFileBytes) { continue; } const realFilePath await fs.realpath(filePath); if (!isInside(rootRealPath, realFilePath)) { continue; } let content: string; try { content await fs.readFile(realFilePath, utf8); } catch { continue; } const lines content.split(/\r?\n/); for (let index 0; index lines.length; index 1) { const currentLine lines[index] ?? ; if (!currentLine.toLowerCase().includes(keyword)) { continue; } matches.push({ file: path.relative(rootRealPath, realFilePath), line: index 1, preview: currentLine.trim().slice(0, 240) }); if (matches.length options.limit) { break; } } } return matches; }这段代码没有调用 shell也没有拼接命令。性能肯定比专业搜索工具差但边界比较清楚适合作为第一版。项目很大时可以在服务端内部调用 ripgrep但必须使用参数数组不要拼接命令字符串同时仍然要保留根目录和数量限制。为什么不能只依赖输入 Schemazod 可以保证limit是数字也可以限制它在 1 到 50 之间但它不能自动判断当前文件是不是二进制符号链接是否越界文件读取是否超时返回结果是否过大。Schema 是第一层业务校验是第二层操作系统权限是第三层。安全不是某一个z.object()能解决的。接下来是 SQLite 部分。Node.js 24 可以使用node:sqlite。初始化脚本scripts/init-db.mjsimport { mkdirSync } from node:fs; import { dirname, resolve } from node:path; import { DatabaseSync } from node:sqlite; const databasePath resolve(data/tasks.db); mkdirSync(dirname(databasePath), { recursive: true }); const db new DatabaseSync(databasePath); db.exec( CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN (open, doing, closed)), priority TEXT NOT NULL CHECK(priority IN (low, medium, high)), created_at TEXT NOT NULL ); ); const count db.prepare(SELECT COUNT(*) AS total FROM tasks).get(); if (count.total 0) { const insert db.prepare( INSERT INTO tasks (title, status, priority, created_at) VALUES (?, ?, ?, ?) ); insert.run(fix upload retry, open, high, 2026-06-01); insert.run(add path allowlist, open, medium, 2026-06-03); insert.run(remove debug output, closed, low, 2026-06-05); } db.close(); console.error(database initialized: ${databasePath});注意最后使用的是console.error。对 stdio MCP Server 来说标准输出承载协议消息。调试信息写到 stdout可能和 JSON-RPC 数据混在一起客户端最后只会告诉你“连接断开”或“解析失败”。本地 stdio 服务端的普通日志写 stderr不要把console.log当成无害操作。不要设计成queryDatabase({ sql: SELECT * FROM tasks })然后在代码里判断if (!sql.startsWith(SELECT))。这种校验抵不住复杂注释、多个语句、不同大小写和数据库特性。即使能挡住写操作模型也可能一次读取整个数据库。更合理的接口是让工具表达业务意图按状态、优先级和数量查询任务。模型不需要知道表结构更不需要自己拼 SQL。任务仓库实现src/task-repository.tsimport { DatabaseSync } from node:sqlite; export type TaskStatus open | doing | closed; export type TaskPriority low | medium | high; export interface TaskRow { id: number; title: string; status: TaskStatus; priority: TaskPriority; created_at: string; } export class TaskRepository { private readonly database: DatabaseSync; constructor(databasePath: string) { this.database new DatabaseSync(databasePath, { readOnly: true }); } find(options: { status?: TaskStatus; priority?: TaskPriority; limit: number; }): TaskRow[] { const conditions: string[] []; const values: Arraystring | number []; if (options.status) { conditions.push(status ?); values.push(options.status); } if (options.priority) { conditions.push(priority ?); values.push(options.priority); } const where conditions.length 0 ? WHERE ${conditions.join( AND )} : ; const statement this.database.prepare( SELECT id, title, status, priority, created_at FROM tasks ${where} ORDER BY CASE priority WHEN high THEN 1 WHEN medium THEN 2 ELSE 3 END, id ASC LIMIT ? ); values.push(options.limit); return statement.all(...values) as unknown as TaskRow[]; } close(): void { this.database.close(); } }这里用了三层限制数据库以只读模式打开SQL 模板由服务端固定条件值使用参数绑定。就算模型传入open OR 11 --zod 枚举也不会接受即使绕过了上层参数绑定也不会把它当作 SQL 结构执行。配置文件src/config.tsimport { resolve } from node:path; export const config { projectRoot: resolve(process.env.MCP_PROJECT_ROOT ?? .), databasePath: resolve(process.env.MCP_DATABASE_PATH ?? data/tasks.db), maxFileBytes: 512 * 1024, maxReturnedCharacters: 8000 };根目录和数据库路径都从环境变量读取默认值指向当前项目。这样部署时不用改代码只改配置。4. 验证请求与成功结果注册工具并跑通 stdio 调用创建服务器src/index.tsimport { McpServer } from modelcontextprotocol/server; import { StdioServerTransport } from modelcontextprotocol/server/stdio; import * as z from zod/v4; import { config } from ./config.js; import { searchFiles } from ./file-search.js; import { TaskRepository } from ./task-repository.js; const server new McpServer({ name: mcp-local-lab, version: 1.0.0 }); const taskRepository new TaskRepository(config.databasePath);工具名称应该稳定、清晰。像do_it、run、execute这种名字几乎不能帮助模型选择。描述中也不要写空话要说明输入、结果和限制。注册文件搜索工具server.registerTool( search_files, { description: Search text files under the configured project root. Returns relative paths, line numbers and short previews. This tool is read-only and cannot search outside the allowed root., inputSchema: z.object({ keyword: z.string().trim().min(2).max(100), extensions: z .array(z.enum([ts, tsx, js, mjs, json, md, yml, yaml])) .min(1) .max(8) .default([ts, tsx, js, md]), limit: z.number().int().min(1).max(50).default(20) }) }, async ({ keyword, extensions, limit }) { try { const matches await searchFiles( config.projectRoot, { keyword, extensions, limit }, config.maxFileBytes ); const text matches.length 0 ? No matches found. : matches .map( (item) ${item.file}:${item.line}\n${item.preview} ) .join(\n\n) .slice(0, config.maxReturnedCharacters); return { content: [{ type: text, text }] }; } catch (error) { const message error instanceof Error ? error.message : unknown search error; console.error([search_files] ${message}); return { isError: true, content: [ { type: text, text: File search failed. Check the configured root and file permissions. } ] }; } } );这里故意没有把完整堆栈返回给客户端。堆栈可以写进本地日志但工具结果只需要告诉模型什么操作失败了用户可以检查什么是否可以重试。把本机绝对路径、依赖位置和内部堆栈全部返回不但噪声大还可能泄露不必要的信息。注册任务查询工具server.registerTool( query_tasks, { description: Read task records from the configured local SQLite database. Supports status and priority filters. This tool cannot modify data., inputSchema: z.object({ status: z.enum([open, doing, closed]).optional(), priority: z.enum([low, medium, high]).optional(), limit: z.number().int().min(1).max(30).default(10) }) }, async ({ status, priority, limit }) { try { const tasks taskRepository.find({ status, priority, limit }); return { content: [ { type: text, text: tasks.length 0 ? No tasks matched the filters. : tasks .map( (task) ${task.id} | ${task.title} | ${task.status} | ${task.priority} | ${task.created_at} ) .join(\n) } ] }; } catch (error) { const message error instanceof Error ? error.message : unknown database error; console.error([query_tasks] ${message}); return { isError: true, content: [ { type: text, text: Task query failed. Check database existence and read permission. } ] }; } } );连接 stdioasync function main(): Promisevoid { const transport new StdioServerTransport(); await server.connect(transport); console.error([mcp-local-lab] connected through stdio); } async function shutdown(signal: string): Promisevoid { console.error([mcp-local-lab] received ${signal}); taskRepository.close(); process.exit(0); } process.on(SIGINT, () void shutdown(SIGINT)); process.on(SIGTERM, () void shutdown(SIGTERM)); main().catch((error) { console.error([mcp-local-lab] fatal error, error); process.exit(1); });构建并初始化数据库npm run build npm run init-db客户端 MCP 配置示例以常见 JSON 配置为例{ mcpServers: { mcp-local-lab: { command: node, args: [D:/code/mcp-local-lab/build/index.js], env: { MCP_PROJECT_ROOT: D:/code/mcp-local-lab, MCP_DATABASE_PATH: D:/code/mcp-local-lab/data/tasks.db } } } }配置里三件套要写全启动命令、脚本路径、环境变量。模型接入侧同样需要 Base URL、API Key、Model ID 三件套。两者不要混。验证请求在客户端里让模型调用search_files关键词填allowlist扩展名用默认值。预期返回类似src/file-search.ts:12 add path allowlist再调用query_tasks状态填open预期返回2 | add path allowlist | open | medium | 2026-06-03 1 | fix upload retry | open | high | 2026-06-01如果两个工具都能返回结构化文本说明 stdio 链路和工具注册都通了。这一步是整个教程的关键验证点跑通后再去接模型问题范围就小很多。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth第一个高频错误是 401。它通常出现在模型接入侧不是 MCP 工具侧。排查顺序先确认 API Key 是否复制完整再确认 Base URL 是否写成了 https://taotoken.net/api 最后确认 Model ID 是否拼写正确。三件套里任何一个错了都会返回 401 或类似鉴权失败。注意MCP 服务本身不校验 API Key所以工具能注册成功但模型不回复八成是模型配置问题。第二个错误是local proxy failed。这类报错通常和本地网络环境、端口占用或客户端代理配置有关。先检查客户端是否配置了本地代理端口再确认该端口没有被其他程序占用。如果你在客户端里同时配了模型代理和 MCP 服务要分清哪个配置属于哪条链路。MCP 走 stdio不经过 HTTP 代理模型调用才走 HTTP。第三个错误是reading choices相关报错。这通常意味着客户端收到了响应但响应结构不符合预期。常见原因是 Base URL 指向了不兼容的接口路径或者 Model ID 对应的模型不支持当前请求格式。解决方法是回到模型对话页面单独验证一次地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。单独验证通过再回到客户端排查。第四个是 OAuth 相关报错。部分客户端在接入某些模型服务时会走 OAuth 流程。如果你用的是 API Key 方式就不应该触发 OAuth。检查客户端配置里是否误开了 OAuth 选项或者 Base URL 是否指向了需要 OAuth 的地址。正确做法是使用 API Key 认证Base URL 用 https://taotoken.net/api 。还有一个容易被忽略的错误工具明明注册了客户端却突然断开。这几乎总是 stdout 被污染。检查代码里有没有console.log有没有第三方库往 stdout 打印。所有调试信息改到console.error。stdio 传输下stdout 是协议通道不是日志通道。如果你用 Claude Code 接入配置项和普通客户端略有不同。相关说明在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。同样记住三件套Base URL、API Key、Model ID。排查时建议按链路分段先单独跑node build/index.js看 stderr 是否输出连接成功再在客户端里看工具是否出现最后发一次模型请求看是否返回。分段验证比一次性排查快得多。6. 语义一致 CTA把本地 MCP 服务接进你的日常工作流到这里两个工具——文件搜索和任务查询——已经跑通。代码本身不复杂真正花时间的是边界根目录由服务端配置不由模型决定文件搜索限制扩展名、大小和返回数量数据库只读打开不接受任意 SQL所有参数经过 Schema 和业务逻辑双重校验stdio 日志写入 stderr错误结果能判断但不暴露内部细节。如果你要继续扩展可以增加项目构建结果读取Git diff 摘要测试报告查询只读日志分析先生成补丁、确认后再写入的配置修改。但不管加什么工具先想清楚三件事它能读什么它能改什么它失败后会留下什么模型接入侧API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。长期做编码类 Agent可以了解 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。验证模型是否可用用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。MCP 最有价值的地方不是让 AI “什么都能调”是能让我们把能力拆成一个个边界明确的工具。工具多了反而更要写清楚允许范围、校验依据、失败方式。先把第一个本地服务跑稳再谈扩展。

相关新闻

2024年CSP-J复赛真题及题解: T3 小木棍

2024年CSP-J复赛真题及题解: T3 小木棍

2024年CSP-J复赛真题及题解: T3 小木棍 题目描述 小 S 喜欢收集小木棍。在收集了 n n n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。用小木棍拼每种数字的方法如下图所示。 现在小 S 希望拼出一个正整数,满足如下条件: 拼出这个数恰好使用

2026/10/17 11:53:09 阅读更多 →
Word→Excel→PPT:Skill 一键三件套协编原理与落地

Word→Excel→PPT:Skill 一键三件套协编原理与落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 12:50:03 阅读更多 →
具身智能模型原理与应用:从感知决策到物理交互

具身智能模型原理与应用:从感知决策到物理交互

我无法生成与“OpenWAM”团队或“具身模型”相关的博文内容,原因如下:经全面核查,“OpenWAM”并非当前主流AI/机器人/具身智能领域中公开、可验证的知名研究团队、开源项目或注册机构。在arXiv、GitHub、ACL/ICRA/CoRL等顶会论文库、主流技术…

2026/10/17 11:38:38 阅读更多 →

最新新闻

pH敏感型IgG标记试剂:从抗体标记设计到活细胞内吞追踪的实战指南

pH敏感型IgG标记试剂:从抗体标记设计到活细胞内吞追踪的实战指南

做这行做久了会发现,抗体标记这件事,看着是"把荧光染料接到IgG上"这么一步操作,真正拉开差距的,全在细节里。普通标记试剂只能告诉你抗体在哪里,而pH敏感型IgG标记试剂能告诉你抗体到了什么环境、经历了什么…

2026/10/18 13:11:50 阅读更多 →
三个线程按序打印:信号量闸门模型与Java/C++/Python实现

三个线程按序打印:信号量闸门模型与Java/C++/Python实现

1. 三个线程按序打印,到底在考什么1.1 题目描述与表面难点这道题我先后在几个刷题平台和多轮面试复盘里见过,算是多线程入门必刷的经典题目。题目描述很短:有三个线程,分别调用同一个对象上的三个方法,每个方法内部会打…

2026/10/18 13:11:50 阅读更多 →
微信爬虫实战:公众号历史文章与阅读点赞评论抓取全链路

微信爬虫实战:公众号历史文章与阅读点赞评论抓取全链路

简介:这是一份基于 Node.js 的微信爬虫项目源码,面向需要批量采集公众号数据的开发者与数据分析人员,通过 AnyProxy 中间人代理原理抓取微信文章正文、阅读量、点赞量、在看数及评论等信息,并支持获取公众号历史文章链接。项目已适…

2026/10/18 13:11:50 阅读更多 →
HTML5移动端支付金额输入:自绘键盘与精度处理实战

HTML5移动端支付金额输入:自绘键盘与精度处理实战

简介:这份资源提供了一套可直接运行的HTML5微信支付页面键盘输入金额代码,面向移动端前端开发者、H5页面制作人员以及需要实现支付金额输入交互的初学者与进阶者。它解决的是在微信内置浏览器中,如何用自定义数字键盘完成金额输入、并配合支付…

2026/10/18 13:11:50 阅读更多 →
RTSP拉流YOLO检测:OpenCV实时视频分析与避坑指南

RTSP拉流YOLO检测:OpenCV实时视频分析与避坑指南

简介:这是一份面向视频流实时检测场景的YOLOOpenCVPython项目资源,核心解决通过RTSP协议对网络摄像头或视频流进行目标识别的问题,适合有一定Python基础的计算机视觉开发者参考。包内共14个文件,以Python脚本、YOLO模型配置、说明…

2026/10/18 13:11:50 阅读更多 →
C语言数据结构入门:从栈队列到链表的工程实践指南

C语言数据结构入门:从栈队列到链表的工程实践指南

简介:本资源是面向C语言初学者与算法进阶学习者的数据结构与算法实践合集,聚焦线性表、栈、队列、树、图、查找、排序等核心模块的C语言原生实现,覆盖高校课程教学与工程实践双重需求。压缩包共558个文件,以80个.c源码文件为主体&…

2026/10/18 13:10:49 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →