@doist/mcp-core

中文文档
0.4.1发布于 2026/11/11MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 5535

Doist MCP 服务器共享核心

此软件包已被标记为弃用

Moved to @todoist/mcp-core. Replace @doist/mcp-core with @todoist/mcp-core in your dependencies and imports.

中文使用文档
文档目录10 节
  1. 安装
  2. 里面有什么东西
  3. 使用量
  4. 定义工具
  5. 登记工具
  6. 结果的线形
  7. 从主机为服务器服务
  8. 合同测试
  9. 工具规则
  10. 许可证

@ doist/ mcp 分数

Todoist MCP服务器共享核心(待办事宜- mcp, (中文(简体) ), 通讯- mcp 和Automations MCP服务器:工具合同,工具注册,工具结果如何塑造给电线,合同一个服务器提供为它服务的主机,合同测试套件每个服务器运行在自己的工具上.

TypeScript, 仅限无害环境管理, 第24节, npm 第11节.

安装

npm install @doist/mcp-core

MCP SDK和Zod是同行的依赖关系,因此服务器和核心各共享一份. 这很重要:一个为多个 MCP 服务器服务的主机将它们解开 instanceof McpServer与第二本SDK相对抗的失败.

端点范围
@modelcontextprotocol/server^2.2.0
zod^4.3.0

./testing 进口 vitest (4.1或以后)和 @modelcontextprotocol/client (2.2或后),来自服务器本身的devDependency. 他们故意不是同伴: npm在生产中安装了生产依赖的同伴,所以宣布他们将在每个服务器的Vitest中放入 npm ci --omit=dev。 。 。 .

里面有什么东西

模块主要出口
toolMcpTool, (中文(简体) ), AnyMcpTool, (中文(简体) ), ToolResult, (中文(简体) ), ExecuteResult, (中文(简体) ), RequiredToolAnnotations, (中文(简体) ), ToolSchemaSource (类型)
register-toolcreateToolRegistrar, (中文(简体) ), ToolRegistrarOptions, (中文(简体) ), RegisterTool, (中文(简体) ), ToolFailure
tool-outputgetToolOutput, (中文(简体) ), getErrorOutput, (中文(简体) ), ToolOutputOptions
sanitizeremoveNullFields, (中文(简体) ), RemoveNullFieldsOptions
tool-schemasgetToolSchemas, (中文(简体) ), ToolSchemas
annotationsgetMcpAnnotations, (中文(简体) ), formatToolTitle
app-metanormalizeToolMeta
host (顺从)validateBearerToken, (中文(简体) ), ValidateBearerTokenOptions, (中文(简体) ), TokenValidationError, (中文(简体) ), TokenValidationResult, (中文(简体) ), ValidateToken, (中文(简体) ), McpServerOptions, (中文(简体) ), isTransientValidationError, (中文(简体) ), buildBearerChallenge, (中文(简体) ), createTokenVerdictCache
testing (顺从)describeOutputSchemaNullability, (中文(简体) ), describeAdvertisedToolSchemas, (中文(简体) ), describeToolAnnotations, (中文(简体) ), describeTokenFootprint, (中文(简体) ), measureTokenFootprint, (中文(简体) ), findNullablePaths, (中文(简体) ), listAdvertisedTools

使用量

定义工具

McpTool<Params, Output, Context> 是对每个呼叫的通用,所以每个服务器会选择自己的上下文:一个API客户端,或者一个携带一个带有中止信号和进度报告器的对象. 一个服务器通常用所填上下文来代替它.

import type { McpTool } from '@doist/mcp-core'
import { z } from 'zod'
import type { NotesApi } from './notes-api.js'

type NotesTool<P extends z.ZodRawShape, O extends z.ZodRawShape = Record<string, never>> = McpTool<
    P,
    O,
    NotesApi
>

export const findNotes = {
    name: 'find-notes',
    description: 'Finds notes whose text contains the search term.',
    parameters: { searchText: z.string().describe('Text to look for.') },
    outputSchema: { notes: z.array(z.object({ id: z.string(), text: z.string() })) },
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
    async execute({ searchText }, api) {
        const notes = await api.search(searchText)
        return { textContent: `Found ${notes.length} notes.`, structuredContent: { notes } }
    },
} satisfies NotesTool<
    { searchText: z.ZodString },
    { notes: z.ZodArray<z.ZodObject<{ id: z.ZodString; text: z.ZodString }>> }
>

登记工具

createToolRegistrar 返回在服务器上注册工具的函数。 产品之间有差异的一切都通过它的选择:

import { createToolRegistrar } from '@doist/mcp-core'
import { McpServer } from '@modelcontextprotocol/server'

export function getMcpServer({ token }: { token: string }): McpServer {
    const server = new McpServer({ name: 'notes-mcp-server', version: '1.0.0' })
    const api = new NotesApi(token)

    const register = createToolRegistrar<NotesApi>({
        titlePrefix: 'Notes',
        createContext: () => api,
        formatError: (error) => (error instanceof Error ? error.message : 'Something went wrong.'),
        // Optional: runs around every call, for retries, request tagging or rewriting the result.
        wrapExecute: (tool, run) => withRetry(run),
        // Optional: replaces the default log line, which never includes the arguments.
        onError: ({ tool, error }) => logger.error({ tool: tool.name, error }),
    })
    for (const tool of [findNotes]) register(server, tool)

    return server
}

对于每个工具:

  • 将输入和输出计划汇编成JSON Schema 2020-12,每个进程一次,但许多服务器都予以登记;
  • 标题 "<titlePrefix>数字 : <Title Case Name>" 和默认 openWorldHint 作假,工具本身的注解获胜;
  • 填写 MCP Apps UI 资源的两种形式(_meta.ui.resourceUri 财务报告和审定财务报表 _meta['ui/resourceUri']当工具设置时;
  • 形状 getToolOutput,以下;
  • 抓到任何东西 execute 扔出,传到 onError返回时 formatError给模型的文本作为 isError 结果.

createContext 收到工具和SDK的 ServerContext,所以一个长期工具可以读取 serverContext.mcpReq.signal 或发送进度通知.

结果的线形

getToolOutput 转出 ToolResult 改为 a CallToolResult数字 :

  1. 裸体和空物体被剥去 structuredContent (单位:千美元)removeNullFields) (中文(简体) ). 空数组留相. 过 verbatimKeys 给那些虚构的钥匙 比如一个存储的JSON Schema.
  2. content 按照顺序:工具本身 contentItems,则 structuredContent 作为JSON文本块,那么 textContent。只读取文本的客户端 content[0] (OpenAI 响应 API 下降) structuredContent仍然得到有效载荷.
  3. structuredContentAsText: false 离开JSON的封锁, 用于部署 其客户全部读取 structuredContent。 。 。 .

从主机为服务器服务

@doist/mcp-core/host 是服务器软件包与服务于其中几个软件主机之间的合同,例如 todoist-ai-Institutions。 每个服务器输出相同的两个形状:

  • getMcpServer(options),则 McpServerOptions (单位:千美元){ token, baseUrl? }),或 McpServerOptions & { ... } 当需要更多时;
  • (单位:千美元) ValidateToken数字 : (token, baseUrl?) => Promise<'valid' | 'invalid' | 'forbidden'>。 。 。 .

验证者只在符上做出裁决并拒绝其他任何内容,所以主机可以重试一次失败而不把它作为裁决. validateBearerToken 仅从状态构建一个 GET数字 :

import { validateBearerToken, type ValidateToken } from '@doist/mcp-core/host'

export const validateNotesToken: ValidateToken = (token, baseUrl = 'https://notes.example.com') =>
    validateBearerToken({ url: `${baseUrl}/api/v1/me`, token })
API 答案validateBearerToken
第2条valid
401 (中文(简体) )invalid: 主机发送401个挑战,客户端刷新其符号
403 (韩语)forbidden,或任何东西 classifyForbidden(response) 返回时
任何其它状态,包含重定向掷出 TokenValidationError,其 httpStatusCode 让主机将 5xx 作为瞬态
没有 timeoutMs (第5类)拒绝 TimeoutError
网络失败以未更改的错误拒绝

身体被排出未读取,所以改变反应的外形不能使一个符号被错误地分类,连接会回到池中去. 重新定向没有被遵循:一个签入的页面,如果答案200的话会通过一个被接受的符号. 过 classifyForbidden 当一个403也可以意味着拒绝 同意不能解除, headers 标记请求,以及 fetch 当服务器的 API 客户端有自己的传输(从 HTTPS_PROXY因此,验证的方式 与其它呼叫一样.

在主机方面,有三个帮助者将验证器的答案变成响应,无论HTTP框架如何:

import {
    buildBearerChallenge,
    createTokenVerdictCache,
    isTransientValidationError,
} from '@doist/mcp-core/host'

const verdicts = createTokenVerdictCache({ ttlMs: 120_000, maxEntries: 10_000 })

async function checkToken(token: string, baseUrl: string): Promise<Response | 'proceed'> {
    let result = verdicts.get(token, baseUrl)
    if (!result) {
        try {
            result = await validateNotesToken(token, baseUrl)
        } catch (error) {
            if (isTransientValidationError(error)) return 'proceed' // or a 503; never cached
            throw error
        }
        verdicts.set(token, baseUrl, result)
    }
    if (result === 'valid') return 'proceed'
    const forbidden = result === 'forbidden'
    return new Response(null, {
        status: forbidden ? 403 : 401,
        headers: {
            'WWW-Authenticate': buildBearerChallenge({
                error: forbidden ? 'insufficient_scope' : undefined,
                resourceMetadataUrl:
                    'https://notes.example.com/.well-known/oauth-protected-resource/mcp',
            }),
        },
    })
}
  • isTransientValidationError:超时,取消或拒绝连接,或5xx,拒绝值重试. 转机或404不是.
  • buildBearerChallenge数字 : Bearer,与 error="insufficient_scope" 财务报告和审定财务报表 resource_metadata="…" 当给予.
  • createTokenVerdictCache: 以符号和基准 URL 键入的判决,保存于 ttlMs最古老的落地时满地.

合同测试

@doist/mcp-core/testing 持有服务器运行在它自己的工具注册簿上的套件 :

import {
    describeAdvertisedToolSchemas,
    describeOutputSchemaNullability,
} from '@doist/mcp-core/testing'
import { getMcpServer } from './mcp-server.js'
import { registeredTools } from './tool-registry.js'

describeOutputSchemaNullability(registeredTools)
describeAdvertisedToolSchemas({
    tools: registeredTools,
    createServer: () => getMcpServer({ token: 'test-token' }),
})
  • describeOutputSchemaNullability: 每个工具一个测试, 任何允许输出字段都失败 null。 。 。 .
  • describeAdvertisedToolSchemas:服务器完全广告这些工具,每个工具都有JSON Schema 2020-12 schemas匹配了直接Zod转换.

都拿一个 { name, parameters, outputSchema? },这样服务器就可以在工具移动到 McpTool。 。 。 .

有两个只读客户端收到的东西,所以他们不重复注册的内容:

import { describeTokenFootprint, describeToolAnnotations } from '@doist/mcp-core/testing'
import { encode } from 'gpt-tokenizer'

const createServer = () => getMcpServer({ token: 'test-token' })

describeTokenFootprint({
    createServer,
    countTokens: (text) => encode(text).length,
    budget: 13_250,
})
describeToolAnnotations({
    createServer,
    expected: {
        'find-notes': {
            title: 'Notes: Find Notes',
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
        },
    },
})
  • describeTokenFootprint: 每届会议支付的固定费用( tools/list 有效载荷为一个阵列,加 instructions保留在 budget一半失踪也失败了 它打印每个工具的成本。 将预算视为权宜之计:需要增加预算,以证明造成预算变化的理由. measureTokenFootprint 返回相同的数字。 标语器是你的,所以Mcp分数 取决于无.
  • describeToolAnnotations:每个广告工具都包含准确的预期说明、标题和缺省(openWorldHint 默认为虚假),每个预期工具都会被公示. 客户端从这些决定一个电话是否需要用户的确认.

工具规则

每个 Doist MCP 服务器都持有这个 核心可以执行一个, 它说如何.

  • 输出计划从未使用 .nullable()。 。 。 . 结果在SDK验证它们之前被剥去无效值,所以一个无效的字段会保证一个工具永远无法发送的值,一旦剥去"输出验证错误",则调用失败. 申报字段 .optional() 并留出密钥(foo: value ?? undefined) (中文(简体) ). describeOutputSchemaNullability 执行这个.
  • 说明描述了该工具最坏的情况. 它们是每个工具,而不是每个行动,所以一个将几个行动分组的工具被附加了最危险的一个. readOnlyHint: true 仅当无事持续存在,且无事发生(文件或可共享链接计数)时. openWorldHint: true 当工具到达用户自己账户以外的任何人时:通知他人,或制作公共URL. destructiveHint: true 当它删除、覆盖、替换先前状态、删除访问权限或以其他方式难以撤消时;发送通知是添加剂而不是破坏性的. RequiredToolAnnotations 3个提示是强制性的.
  • 描述一个输出字段,只在计划不能时进行. 输出计划发送到每个 tools/list,共享的则每个工具内嵌一次。 描述单位和格式、惯例、结合、有条件的存在和不明显的含义;保留任何重复字段名称或列出enum值的内容。 输入描述是一个模型如何学会调用工具,应该像需要的那样完整.
  • 用字符串清除可选字段, 不 null。 。 。 . 一些提供商,特别是双子座公司,对无效输入处理不当. 使用描述式哨兵( I)"unassign", (中文(简体) ), "remove"),并用实地描述记录.
  • 批量写入工具报告每个项目失败, 绝不扔出 . 单枪匹马地解决每件物品,把那些失败的同那些成功的东西一起归还,即使每件物品都失败。 被抛出的错误隐藏了写的东西,所以重试一次写了两次.
  • 不默认日志工具参数 . 写作工具中包含用户内容. 默认 onError 只记录工具名称和出错.

许可证

麻省理工学院

版本与发布记录

@doist/mcp-core 共发布 8 个版本,首次发布于 2026/11/09;过去 12 个月发布了 8 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.1latest2026/11/112 天前
0.4.02026/11/094 天前
0.3.02026/11/094 天前
0.2.02026/11/094 天前
0.1.12026/11/094 天前
0.1.02026/11/094 天前

关于 @doist/mcp-core 的常见问题

@doist/mcp-core 是什么?

Doist MCP 服务器共享核心注意:该包已被作者标记为弃用——Moved to @todoist/mcp-core. Replace @doist/mcp-core with @todoist/mcp-core in your dependencies and imports.

如何安装 @doist/mcp-core?

在项目目录运行 npm install @doist/mcp-core,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4.3.0)、@modelcontextprotocol/server(^2.2.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@doist/mcp-core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@doist/mcp-core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@doist/mcp-core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@doist/mcp-core 可以商用吗?

@doist/mcp-core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@doist/mcp-core 的下载量有多少?

根据 npm 官方下载统计,@doist/mcp-core 在 2026/10/13 – 2026/11/11 这 30 天内被下载 5,535 次,日均约 185 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@doist/mcp-core 还在维护吗?

最新版本 0.4.1 发布于 2026/11/11(2 天前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/11/09。作者已将其标记为弃用,新项目建议寻找替代方案。