@animalabs/mcpl-core

中文文档
0.4.0发布于 2026/10/26MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 1.5万

MCPL core: JSON-RPC connection, channels, push events, and capability types.

原始使用文档
文档目录8 节
  1. What's in here
  2. Capability advertisement is a tree, and absence is denial
  3. Conformance vectors
  4. Toolsets and tool hints (RFC-014, RFC-015 — drafts)
  5. Install
  6. Usage
  7. Build & test
  8. License

@animalabs/mcpl-core

Published on npm as @animalabs/mcpl-core. Inside the connectome ecosystem it's consumed as a sibling source checkout under the @connectome/mcpl-core dependency key (file:../mcpl-core-ts) — npm keys file: deps by name, so both work side by side.

TypeScript core for MCPL — an MCP-superset wire protocol that adds server-initiated push events, channels (bidirectional, push-driven message streams), conversation branches, inference hooks, host-managed state, and capability / feature-set negotiation on top of plain JSON-RPC.

This package is the shared, transport-agnostic core used across the connectome agents (relays, bridges, hosts). It contains no business logic — just the wire contract and a connection primitive.

What's in here

  • McplConnection — a JSON-RPC 2.0 connection over a readable/writable stream pair (McplConnection.fromStreams(stdin, stdout)), with a pull-based nextMessage() for incoming requests/notifications and promise-based sendRequest() / sendResponse() / sendNotification() / sendError(). Over a stream, each message is one line, ended by \n (the last one also by the end of the input) and by nothing else, so a U+2028 or U+2029 inside a JSON string stays in its message; over a WebSocket (fromWebSocket), each text message is one message. It sends both characters escaped (\u2028, \u2029), so a peer whose line reader splits there, as Node 24's readline does, still reads one message. It closes once: when either stream fails (a TCP reset, a WebSocket error, a child process's broken pipe), when its input ends or is destroyed, when its output is destroyed before it finished, or on close(). An output ended on purpose leaves the input to finish: the connection still reads, and sends nothing more. Closing takes effect before anything is reported: nothing more is sent or read, pending requests reject with ConnectionClosedError, and nextMessage() returns what was already queued and then rejects. Then the stream error that closed it is emitted, once, as 'error', and 'close' follows; once it has closed, nothing more is read and a later stream error isn't reported. A malformed line is emitted as 'error', and so is an exception a 'request' or 'notification' listener throws: an Error as itself, and any other thrown value as the cause of an Error. Neither closes the connection, and the message a listener threw for still reaches nextMessage(). A request drained from the queue after the close can't be answered. A connection built on a stream that was already done is closed from the start, and reports it on a later turn of the event loop, to listeners added before anything else is awaited: that stream's error if it had one, then 'close'. close() also releases the output, once (it destroys the output stream, and closes a WebSocket; an input given to fromStreams stays the caller's), even if a 'close' listener throws, so call it after a close the peer caused, too.
  • method — the method-name constants: initialize, push/event, channels/* (list, open, close, publish, changed, …), branches/*, inference/*, state/*, model/info, …
  • Typed params/results for every method (e.g. ChannelDescriptor, PushEventParams, ChannelsPublishParams).
  • Content blocks — ContentBlock (text / image / audio) plus helpers like textContent(...).
  • Capabilities & feature sets — negotiation types (InitializeCapabilities, McplCapabilities, FeatureSetDeclaration, context/inference hook caps), the closed CAPABILITY_PATHS vocabulary (SPEC §6.2), the recursive advertisement walk (advertisedCapabilities, §5.1), grant matching (capabilityGranted, §5.4) and fail-closed uses validation (validateUses, §6.4).
  • Manifest changes (§17 / RFC-003) — manifestDigest (the canonical content digest), changedDomains, and ManifestTracker: call setManifest(next) and the digest, the diff, the per-connection mcpl/manifestChanged announcement and the mcpl/manifest snapshot all follow. Servers never hand-author an announcement.
  • Errors — ConnectionClosedError, whose message says what the close meant for the call (nothing was sent; it was handed to the connection's output and got no answer; or, from nextMessage(), no more messages will come), and whose outcome is 'not-sent' or 'no-response' for a request and undefined for nextMessage(); RpcError, a JSON-RPC error with its code and data, whose message has no code in front of it: the other end's own message, or a sentence that says what came when it sent none as text; ConnectionTimeoutError, the RpcError (code -32000, outcome 'no-response') a request with no reply in time rejects with; and ERR_* protocol error codes.

Capability advertisement is a tree, and absence is denial

channels, contextHooks.beforeInference and inferenceRequest are objects whose members are the leaves of the §6.2 vocabulary. A boolean true at any level is shorthand for every leaf beneath it; false or absence means none.

const mcpl: McplCapabilities = {
  version: '0.5',
  channels: { register: true, publish: true, incoming: true, streaming: true },
  contextHooks: {
    beforeInference: { observe: false, inject: { beforeUser: true, afterUser: true } },
  },
};

advertisedCapabilities(mcpl);
// Set { 'channels.register', 'channels.publish', 'channels.incoming',
//       'channels.streaming',
//       'contextHooks.beforeInference.inject.beforeUser',
//       'contextHooks.beforeInference.inject.afterUser' }

That is an input to the host's grant computation, never an authorization. effectiveCapabilities (§5.4) is the sole normative allowlist: every path not present is denied. deniedCapabilities is diagnostics and MUST NOT reach an authorization decision.

Conformance vectors

test/vectors/manifest-digest-vectors.json is a verbatim copy of the frozen interop vectors from anima-research/mcpl conformance/manifest-digest-vectors.json. It is not edited here — a vector believed wrong is an issue against that repo. Run a newer checkout with:

MCPL_DIGEST_VECTORS=/path/to/mcpl/conformance/manifest-digest-vectors.json npm test

Toolsets and tool hints (RFC-014, RFC-015 — drafts)

ToolsetServer is a server-side runtime for the draft MCPL RFC-014 (tool presence, toolsets and surfaces). It is a framework, not wire contract, so it lives on its own entry point, @animalabs/mcpl-core/toolsets; the package root carries only the RFC-014/015 method names (method.TOOLSETS_OPEN, method.ACCESS_TOKEN, …) and wire types. A server keeps its full tool list and says, per tool, whether it is static or dynamic, whether it is visible right now, and which toolset it belongs to. Hosts that understand the hints keep dynamic tools out of the model's stable tool list; hosts that don't see the full list, plus toolset_open / toolset_close (and, for toolsets with instances, toolset_use) tools so the server stays usable.

import { McplConnection } from '@animalabs/mcpl-core';
import { ToolsetServer } from '@animalabs/mcpl-core/toolsets';

const server = new ToolsetServer({
  name: 'arcade', version: '1.0.0',
  toolsets: {
    game: {
      title: 'Game', summary: 'A game against the bot', default: 'closed', instances: true,
      tools: [play, pass, resign],                        // MCP tool definitions
      open(ctx, options) {                                // one instance per toolsets/open
        const g = newGame(options);
        return {
          title: 'Game',
          visible: () => (g.myTurn ? ['play', 'pass', 'resign'] : ['resign']),
          content: () => [{ type: 'text', text: g.board() }],
          async call(tool, args) { return g.do(tool, args); },   // results carry their own hint changes
          close() {},
        };
      },
    },
  },
});
await server.serve(McplConnection.fromStreams(process.stdin, process.stdout), 'local');

What it does for you:

  • the toolset methods, with named instances;
  • hint changes in call results, on events, or standalone, with consecutive revs per connection (hosts apply them on arrival, so a refused push is never restated);
  • toolsets/list as a full statement, with ended records (by id) and events held for a principal that was away, delivered after its list;
  • events attributed to the call that caused them (so a call's consequences are never also pushed);
  • instances that belong to the principal and survive reconnects;
  • ctx.access(audience), the server side of RFC-015 (delegated access), which asks the host for a short-lived credential.

Limits to know:

  • Validation. Arguments to every tool, static or in a toolset, and toolset options are checked with a small built-in JSON Schema subset: type, enum, const, properties, required, additionalProperties, items, the min/max keywords, pattern, oneOf/anyOf/allOf. It fills defaults and ignores everything else ($ref, if/then/else, not, format, uniqueItems, …). Pass validate (e.g. ajv) if your schemas use those.
  • Lifetime. Instances outlive a connection, so a reconnect finds them. After a principal's last connection closes they stay open for idleCloseMs (default 24 h), then are closed and the principal forgotten; idleCloseMs: 0 keeps them until the process exits.
  • Grants. An MCPL host that has not granted tools, or has disabled the server's <name>.tools feature set, gets no calls or toolset methods (-32002 / -32001). A plain MCP host is served as MCP.

Install

npm install @animalabs/mcpl-core

Usage

import { McplConnection, ConnectionClosedError, textContent, method } from '@animalabs/mcpl-core';

const conn = McplConnection.fromStreams(process.stdin, process.stdout);

// Stream errors (a reset, a broken pipe), malformed lines and exceptions
// thrown by 'request' or 'notification' listeners arrive here. A stream
// destroyed without an error closes the connection with no 'error'.
// Like any EventEmitter, a connection with no 'error' listener throws them.
conn.on('error', (err) => console.error('mcpl:', err.message));

// server-initiated push, at any time while the connection is open:
conn.sendNotification(method.PUSH_EVENT, { /* … */ });

try {
  while (true) {
    let msg;
    try {
      msg = await conn.nextMessage();
    } catch (err) {
      // The connection closed: a stream ended or failed, or close() was called.
      if (err instanceof ConnectionClosedError) break;
      throw err;
    }
    if (msg.type === 'request' && msg.request.method === 'tools/list') {
      conn.sendResponse(msg.request.id, { tools: [] });
    }
  }
} finally {
  // Releases the output (stdout here), whichever side closed the connection.
  // The input stays this program's: the connection only stops reading it.
  conn.close();
}

Build & test

npm install
npm run build   # tsc → dist/
npm test

Node 22.12 or later is supported, as engines says. CI builds and tests on Linux with 22.12.0, 24 and the newest Node, and builds on macOS with 22.12.0 and the newest.

License

MIT — see LICENSE.

版本与发布记录

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

版本发布日期距今
0.4.0latest2026/10/2613 天前
0.3.02026/08/202 个月前
0.2.22026/07/283 个月前
0.2.12026/07/143 个月前
0.2.02026/07/044 个月前
0.1.02026/06/154 个月前

关于 @animalabs/mcpl-core 的常见问题

@animalabs/mcpl-core 是什么?

MCPL core: JSON-RPC connection, channels, push events, and capability types.

如何安装 @animalabs/mcpl-core?

在项目目录运行 npm install @animalabs/mcpl-core,或使用 pnpm add / yarn add / bun add。

@animalabs/mcpl-core 支持 TypeScript 吗?

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

@animalabs/mcpl-core 支持 ESM 还是 CommonJS?

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

@animalabs/mcpl-core 需要什么版本的 Node.js?

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

@animalabs/mcpl-core 可以商用吗?

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

@animalabs/mcpl-core 的下载量有多少?

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

@animalabs/mcpl-core 还在维护吗?

最新版本 0.4.0 发布于 2026/10/26(13 天前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/06/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。