npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ctp-registry

v0.2.0

Published

Component Tool Protocol (CTP) - A standardized protocol for UI components to declaratively expose tool capabilities to LLM Agents

Readme

CTP Registry

npm version License: MIT

Component Tool Protocol (CTP) — 让 UI 组件声明式地向 LLM Agent 暴露工具能力的标准化协议

什么是 CTP

CTP(Component Tool Protocol)是一个标准化协议,定义了前端 UI 组件如何向 LLM Agent 声明和暴露工具能力。

核心思想:组件自治 + Agent 自动发现

每个 UI 组件在挂载时主动声明「我能做什么」,Agent 无需硬编码即可动态发现和调用所有可用工具。这就像前端组件级别的 MCP。

特性

  • 框架无关 — 核心 ToolRegistry 零依赖,React/Vue 适配层为可选
  • OpenAI 兼容getTools() 直接输出 function-calling 格式
  • 生命周期感知 — 挂载注册、卸载清理、切换页面工具自动消失
  • 命名空间隔离 — 每个 Provider 有独立 scope,工具名不冲突
  • 结构化错误 — 6 种 CTPErrorCode,LLM 能区分错误类型并自我修正
  • 中间件管线 — 可插拔 middleware,支持日志/审计/限流
  • 执行历史 — 可选 call history,支持多轮对话上下文回溯
  • 条件可用性 — 工具可动态启用/禁用,Agent 自动感知

安装

npm install ctp-registry
# or
yarn add ctp-registry
# or
pnpm add ctp-registry

快速开始

1. 组件注册工具

import { toolRegistry } from 'ctp-registry';

// 视频面板注册两个工具
const unregister = toolRegistry.register('video-panel', {
  description: '视频面板 — 相机查询与帧截取',
  tools: [
    {
      name: 'list_camera_topics',
      description: '列出当前可用的相机 video topics',
      parameters: { type: 'object', properties: {} },
      execute: async () => {
        const topics = getVideoTopics();
        return { topics, count: topics.length };
      },
    },
    {
      name: 'capture_frame',
      description: '截取指定相机的当前画面',
      parameters: {
        type: 'object',
        properties: {
          topic: { type: 'string', description: '相机 topic 名称' },
          time_s: { type: 'number', description: '可选,跳转到指定时间后截取' },
        },
        required: ['topic'],
      },
      execute: async ({ topic, time_s }) => captureFrame(topic, time_s),
    },
  ],
});

// 组件销毁时调用
unregister();

2. Agent 发现工具

const tools = toolRegistry.getTools();
// [
//   { type: 'function', function: {
//       name: 'video-panel__list_camera_topics',
//       description: '[video-panel] 列出当前可用的相机 video topics',
//       parameters: { type: 'object', properties: {} }
//   }},
//   { type: 'function', function: {
//       name: 'video-panel__capture_frame',
//       ...
//   }},
// ]

// 直接传给 LLM — 无需转换
const response = await openai.chat.completions.create({
  model: 'gpt-4',
  messages,
  tools,   // ← CTP 生成的工具列表
  tool_choice: 'auto',
});

3. 路由调用

// LLM 选择了 video-panel__capture_frame
const { name, arguments: rawArgs } = toolCall.function;

if (toolRegistry.has(name)) {
  const result = await toolRegistry.call(name, JSON.parse(rawArgs));
  // → 自动路由到 video-panel 的 capture_frame
  // → 参数校验 → 中间件管线 → execute() → 返回结果
  // → 结果回传给 LLM 继续对话
}

React 集成

import { useToolProvider, useToolDiscovery } from 'ctp-registry/react';

// 组件内声明工具
function VideoPanel({ videoTopics }) {
  useToolProvider('video-panel', {
    description: '视频面板',
    tools: [
      {
        name: 'capture_frame',
        description: '截取当前画面',
        parameters: { type: 'object', properties: {} },
        execute: async () => takeScreenshot(),
      },
    ],
  }, [videoTopics]); // deps 变化时重新注册

  return <div>...</div>;
}

// Agent 面板发现工具
function AgentPanel() {
  const ctpTools = useToolDiscovery();

  const handleToolCall = async (name: string, args: object) => {
    if (toolRegistry.has(name)) {
      return await toolRegistry.call(name, args);
    }
  };

  return <Chat tools={ctpTools} onToolCall={handleToolCall} />;
}

Vue 集成

<script setup>
import { useToolProvider, useToolDiscovery } from 'ctp-registry/vue';

const props = defineProps(['videoTopics']);

// 组件内声明工具
useToolProvider('video-panel', () => ({
  description: '视频面板',
  tools: [
    {
      name: 'capture_frame',
      description: '截取当前画面',
      parameters: { type: 'object', properties: {} },
      execute: async () => takeScreenshot(),
    },
  ],
}), [() => props.videoTopics]);

// Agent 面板发现工具
const tools = useToolDiscovery();
</script>

高级特性

结构化错误体系

import { CTPErrorCode } from 'ctp-registry';

// 错误返回 JSON,LLM 可解析
{
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Parameter validation failed",
    "details": {
      "errors": [{
        "field": "topic",
        "expected": "present",
        "actual": "missing",
        "message": "Required parameter \"topic\" is missing"
      }]
    }
  }
}

中间件管线

// 日志中间件
const removeLogger = toolRegistry.use(async (ctx, next) => {
  console.debug('[CTP] →', ctx.qualifiedName, ctx.args);
  const t0 = performance.now();
  const result = await next();
  const ms = (performance.now() - t0).toFixed(0);
  console.debug('[CTP] ←', ctx.qualifiedName, ms + 'ms');
  return result;
});

// 权限中间件
toolRegistry.use(async (ctx, next) => {
  if (ctx.scope === 'admin' && !currentUser.isAdmin) {
    return JSON.stringify({
      error: { code: 'FORBIDDEN', message: 'Admin only' }
    });
  }
  return next();
});

// 移除中间件
removeLogger();

执行历史

// 启用历史记录(最多保留 50 条)
toolRegistry.enableHistory({ limit: 50 });

// 获取最近 10 条调用记录
const recent = toolRegistry.getHistory({ limit: 10 });
// → [{ qualifiedName, args, result, durationMs, success, timestamp }]

// 传给 LLM 作为上下文
const systemPrompt = `Recent tool calls: ${JSON.stringify(recent)}`;

// 关闭历史
toolRegistry.disableHistory();

条件可用性

toolRegistry.register('player', {
  tools: [{
    name: 'capture',
    description: '截取当前画面',
    parameters: { type: 'object', properties: {} },
    execute: async () => takeScreenshot(),
    // 静态禁用
    enabled: false,
    disabledReason: '当前无视频流',
  }, {
    name: 'seek',
    description: '跳转到指定时间',
    parameters: { type: 'object', properties: { time_s: { type: 'number' } } },
    execute: async ({ time_s }) => seekTo(time_s),
    // 动态判断
    enabled: () => playerState === 'playing',
    disabledReason: '播放器未处于播放状态',
  }],
});

// getTools() 默认只返回 enabled 的工具
// getTools({ includeDisabled: true }) 返回所有

API 参考

核心 API

| 方法 | 签名 | 描述 | |------|------|------| | register | (scope, options) => () => void | 注册 Provider,返回卸载函数 | | getTools | (filter?) => OpenAITool[] | 获取工具,OpenAI function-calling 格式 | | call | (qualifiedName, args?) => Promise<string> | 调用工具(含校验+中间件) | | has | (qualifiedName) => boolean | 检查工具是否注册 | | use | (middleware) => () => void | 注册中间件 | | subscribe | (callback) => () => void | 订阅变化事件 | | enableHistory | (options?) / disableHistory() | 启用/禁用执行历史 | | getHistory | (options?) => HistoryEntry[] | 获取执行历史 | | getProviders | () => ProviderInfo[] | 获取所有 Provider 摘要 | | updateTool | (scope, name, patch) | 局部更新工具属性 |

React Hooks

| Hook | 描述 | |------|------| | useToolProvider | 挂载注册,卸载清理,deps 变化重注册 | | useToolDiscovery | 响应式工具发现(基于 useSyncExternalStore) | | useToolHistory | 跟踪工具执行历史 | | useToolRegistry | 访问 ToolRegistry 实例 |

Vue Composables

| Composable | 描述 | |------------|------| | useToolProvider | 挂载注册,卸载清理,deps 变化重注册 | | useToolDiscovery | 响应式工具发现 | | useToolHistory | 跟踪工具执行历史 | | useToolRegistry | 访问 ToolRegistry 实例 |

命名规范

全限定名格式

格式:  scope__toolName
分隔:  双下划线 __ (兼容 OpenAI / Anthropic / Bedrock API)
字符:  [a-zA-Z0-9_-]

示例:
  nsv-data__get_nsv_frame_data
  video-panel__capture_frame
  page-context__get_task_run_info

建议

  • Scope: kebab-case,表达组件职责(nsv-data 而非 left-panel
  • 工具名: snake_case,动词开头(get_/fetch_/list_/capture_
  • 描述: 要对 LLM 友好 — 它是 LLM 理解工具用途的唯一依据

CTP 与 MCP 的关系

| 协议 | 定位 | 使用场景 | |------|------|----------| | MCP (Model Context Protocol) | 跨进程/网络的远程工具协议 | 知识库检索、代码执行服务器 | | CTP (Component Tool Protocol) | 前端组件级别的本地工具协议 | 视频截取、数据查询、UI 操作 |

两者互补,在实际项目中共存:

  • CTP 工具:前端组件声明的本地能力
  • MCP 工具:远端服务器提供的能力
  • Agent 同时发现 CTP + MCP 工具,统一传给 LLM

架构图

┌──────────────┐     ┌──────────────┐     ┌────────────┐
│        地图面板         │     │      视频播放器         │     │         数据表格     │
│                        │     │                       │     │                     │
│     我能:              │     │    我能:              │     │     我能:           │
│      · 定位坐标         │     │      · 截取画面        │     │      · 查询数据       │
│      · 切换图层        │      │      · 跳转时间        │     │      · 导出 CSV       │
└──────┬───────┘     └──────┬───────┘     └──────┬──────┘
       │ register           │ register           │ register
       ▼                    ▼                    ▼
┌─────────────────────────────────────────────┐
│                       ToolRegistry (单例)                                │
│                                                                         │
│        map__locate         player__capture     table__query             │
│        map__switch_layer   player__seek        table__export             │
└────────────────────────┬───────────────────┘
                                          │ getTools() → OpenAI 格式
                                          │ call(name, args) → 路由执行
                                          ▼
                        ┌──────────────┐
                        │      LLM Agent        │
                        │      发现 6 个工具      │
                        │      自主选择调用       │
                        └──────────────┘

协议优化建议

基于 CTP.md 文档,以下是一些协议优化建议:

  1. 类型安全增强: 使用 TypeScript 泛型确保工具参数和返回值的类型安全
  2. 批量操作: 支持批量注册/调用工具,减少往返次数
  3. 工具版本控制: 添加版本字段支持工具演进
  4. 依赖注入: 支持工具间的依赖关系声明
  5. 沙箱执行: 可选的沙箱环境执行不可信工具代码
  6. 性能监控: 内置性能指标收集(执行时间、内存使用等)
  7. 缓存机制: 支持工具结果的智能缓存

贡献

欢迎提交 Issue 和 PR!

许可证

MIT