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

pty-session-mcp

v0.0.5

Published

本机 PTY 会话:浏览器直连 SDK + Node MCP/HTTP 服务。

Readme

pty-session-mcp

本机 PTY 会话服务:同一套 create / write / read / resize / kill / list,给浏览器走 HTTP + WebSocket SDK,给 Agent 走 MCP。

默认 npm 入口只打包浏览器客户端(fetch + WebSocket,约 5.6KB),不会把 node-pty、MCP SDK 打进前端。服务端从 pty-session-mcp/server 引入。

浏览器 (xterm.js + LocalTerminalClient)
        │  HTTP / WebSocket
        ▼
pty-session-mcp serve   ── PTY (bash / $SHELL)
        │
        └── /mcp 或 stdio  ── Cursor / Claude

要求 Node.js ≥ 20。启动 MCP 或 daemon 必须设置 LOCAL_TERMINAL_TOKEN,为空会直接退出。

安装

npm install pty-session-mcp

本仓库(Rush):

rush update
rush build --to pty-session-mcp

| 入口 | 给谁 | 内容 | |------|------|------| | pty-session-mcp / pty-session-mcp/client | 前端 | LocalTerminalClient | | pty-session-mcp/server | Node | SessionManager、MCP、HTTP daemon | | pty-session-mcp CLI | 进程 | 无参数 = stdio MCP;serve = HTTP/WS |

所有操作都走 options 对象。未识别字段会透传(zod .passthrough()),旧服务忽略、新字段以后再接。

快速开始

在跑终端的那台机器(或容器)上起 daemon:

LOCAL_TERMINAL_TOKEN=secret npx pty-session-mcp serve --host 0.0.0.0 --port 8080
  • 默认绑 0.0.0.0:7788。前端经过网关 / 预览域名访问时必须绑 0.0.0.0,只绑 127.0.0.1 会得到 connect error 111(ECONNREFUSED)。
  • 前端 url 填页面能访问到的地址(预览域名或 http://127.0.0.1:8080),不要填容器内部 hostname 除非浏览器也能解析。
  • HTTP 带 Authorization: Bearer <token>。浏览器 WebSocket 不能自定义 header,订阅流用 ?token=。

前端:xterm.js

这是主要用法。按键必须走原始输入,不要用 HTTP write 的默认 submit: true(那会给每个字符补 \n)。

import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { LocalTerminalClient } from 'pty-session-mcp';
import '@xterm/xterm/css/xterm.css';

const term = new Terminal({ convertEol: true });
const fit = new FitAddon();
term.loadAddon(fit);
term.open(document.getElementById('terminal')!);
fit.fit();

const client = new LocalTerminalClient({
  url: 'https://your-preview-host',
  token: 'secret',
});

const session = await client.create({
  cols: term.cols,
  rows: term.rows,
  // cwd 不传则默认 /home
});
const stream = client.subscribe(session.id);

term.onData((data) => stream.write(data));
term.onResize(({ cols, rows }) => stream.resize(cols, rows));
window.addEventListener('resize', () => fit.fit());

for await (const chunk of stream) {
  term.write(chunk);
}

// 离开页面时
stream.close();
await client.kill({ id: session.id });

要点:

  • create 时传入当前 term.cols / term.rows,否则 PTY 按 80×24 排版,和画面会对不齐。
  • subscribe() 连上会先回放环形缓冲里的已有输出(prompt 不会丢),再推增量。
  • stream.write / stream.resize 走同一条 WebSocket,不加换行。
  • 窗口变化:FitAddon 触发 onResize → stream.resize,PTY 才会跟着变(vim / less / 换行才正确)。
  • for await 在进程退出或 stream.close() 后结束。

不用 xterm、只发命令

import { LocalTerminalClient } from 'pty-session-mcp';

const client = new LocalTerminalClient({
  url: 'http://127.0.0.1:8080',
  token: 'secret',
});

const { id } = await client.create();
await client.write({ id, data: 'ls -la' }); // submit 默认 true,会补 \n
const out = await client.read({ id, waitMs: 2000 });
console.log(out.data);
await client.kill({ id });

LocalTerminalClient 方法:create / write / read / resize / kill / list / subscribe。

subscribe(id) 返回:

| 成员 | 说明 | |------|------| | for await (const chunk of stream) | 服务端输出(含连上时的回放) | | stream.write(data) | 原始输入,submit: false | | stream.resize(cols, rows) | 通知 PTY 改尺寸 | | stream.close() | 关掉 WebSocket,不杀进程 |

后端:MCP

Cursor / Claude(stdio)

{
  "mcpServers": {
    "pty-session": {
      "command": "npx",
      "args": ["-y", "pty-session-mcp"],
      "env": {
        "LOCAL_TERMINAL_TOKEN": "replace-with-a-long-random-secret"
      }
    }
  }
}

本仓库开发时可以把 command 换成 node,args 换成 ["libraries/local-terminal-mcp/dist/cli.js"]。

工具名与 HTTP 相同:create、write、read、resize、kill、list。Agent 发命令用 write(默认补换行);交互程序把 submit 设为 false。

进程内挂 MCP 或开 HTTP

import {
  SessionManager,
  createMcpServer,
  startDirectServer,
  startMcpStdio,
  requireToken,
} from 'pty-session-mcp/server';

const token = requireToken();
const manager = new SessionManager({
  cwdAllowList: ['/home'],
  bufferSize: 1024 * 1024,
  defaultCols: 80,
  defaultRows: 24,
});

await startDirectServer(manager, { token, host: '0.0.0.0', port: 7788 });

stdio MCP:

await startMcpStdio(manager, '0.0.3');

serve 同时提供 REST、WebSocket 和 MCP Streamable HTTP(/mcp)。一律要 token。

SessionManager 选项:

| 字段 | 默认 | 说明 | |------|------|------| | bufferSize | 1 MiB | 每个会话环形输出缓冲 | | defaultCols / defaultRows | 80 / 24 | create 未传尺寸时使用 | | cwdAllowList | 不限制 | 只允许这些目录及其子目录作为 cwd |

Docker 与默认用户

面向容器:daemon 以 root 跑(USER root 或 docker run -u 0),PTY 默认 uid/gid 为 0。

  • 未指定 cwd 时依次试:/home → process.cwd() → 家目录 → 临时目录。
  • 以 root 启动时,子进程 HOME/USER/LOGNAME 设为 /root / root。
  • 进程本身不是 root 时不会 setuid,子进程仍是当前用户。这时若前端硬传 cwd: '/root/workspace',会因 /root 为 drwx------ 而失败。

本地非 root 开发不受影响:跳过进不去的 /root,落到可用目录。

操作

MCP 工具、SDK 方法、REST 路径共用同一套 options。

create

开一个持久 PTY。默认 $SHELL 或 /bin/bash。TERM 为 xterm-256color。新建会话会强制 LANG / LC_ALL / LC_CTYPE 为 C.UTF-8,避免中文乱码。

| 字段 | 类型 | 说明 | |------|------|------| | cwd | string | 工作目录。未传时依次试 /home、process.cwd()、家目录、临时目录 | | cols / rows | number | 默认 80 × 24 | | env | Record<string, string> | 覆盖子进程环境。LANG / LC_ALL / LC_CTYPE 仍固定为 C.UTF-8 | | name | string | 展示名,默认等于 id | | command | string | 可执行文件 | | args | string[] | 参数 | | uid / gid | number | 默认 0。仅当 daemon 自身是 root 时才会真正 setuid |

返回:{ id, name, pid, cwd, cols, rows, createdAt, running }。id 形如 t_<uuid>。

write

| 字段 | 类型 | 说明 | |------|------|------| | id | string | 必填 | | data | string | 写入内容 | | submit | boolean | HTTP/MCP 默认 true:末尾没有 \n / \r 时自动补 \n。xterm.js 按键必须 false,或走 subscribe().write() |

返回:{ id, bytes }。

read

增量读输出。把上次返回的 cursor 传回 since。适合轮询;实时画面用 subscribe。

| 字段 | 类型 | 说明 | |------|------|------| | id | string | 必填 | | since | number | 字节游标,默认 0(缓冲起点) | | waitMs | number | 没有新输出时最多阻塞多久,默认 0(立即返回) | | maxBytes | number | 本次最多返回多少字节 |

返回:{ id, data, cursor, running, exitCode?, dropped? }。dropped 表示环形缓冲溢出、在这次读取之前丢掉的字节。

resize

同步 PTY 尺寸。xterm.js onResize / FitAddon 之后调用。

| 字段 | 类型 | 说明 | |------|------|------| | id | string | 必填 | | cols / rows | number | 正整数 |

返回当前 SessionInfo(含更新后的 cols / rows)。会话已退出时失败。

kill

| 字段 | 类型 | 说明 | |------|------|------| | id | string | 必填 | | signal | string \| number | 默认 SIGTERM |

返回:{ id, ok: true, running, exitCode? }。

list

列出当前进程里还活着的会话。options 预留给以后的过滤字段。

每项即 SessionInfo:id、name、pid、cwd、cols、rows、createdAt、running、exitCode?。

REST

鉴权:HTTP 用 Authorization: Bearer <token>;WebSocket 也可用 ?token=。未带或错误返回 401。OPTIONS 预检不校验 token(CORS)。

成功:{ ok: true, data }。失败:{ ok: false, error: { code, message } }。

| 方法 | 路径 | 操作 | |------|------|------| | POST | /sessions | create,JSON body | | GET | /sessions | list | | GET | /sessions/:id?since=&waitMs=&maxBytes= | read | | POST | /sessions/:id/write | write,JSON body | | POST | /sessions/:id/resize | resize,JSON body | | DELETE | /sessions/:id?signal= | kill | | GET | /sessions/:id/stream | WebSocket | | GET | /health | 健康检查(同样要 token) | | * | /mcp | MCP Streamable HTTP |

WebSocket /sessions/:id/stream

连上后服务端先发缓冲回放,再推增量。会话退出时发 exit 并关闭连接。未知 session 以 code 4004 关闭。

服务端 → 客户端:

{ "type": "data", "data": "..." }
{ "type": "exit", "exitCode": 0 }

客户端 → 服务端(subscribe().write / resize 已封装):

{ "type": "write", "data": "...", "submit": false }
{ "type": "resize", "cols": 120, "rows": 40 }

非 JSON 文本按原始输入处理(submit: false)。WS 上 write 的 submit 默认 false(和 HTTP 相反),方便 xterm 按键。

CLI

# stdio MCP(给 Cursor)
LOCAL_TERMINAL_TOKEN=secret pty-session-mcp

# HTTP / WS / MCP HTTP
LOCAL_TERMINAL_TOKEN=secret pty-session-mcp serve --host 0.0.0.0 --port 8080

| 变量 / 参数 | 说明 | |-------------|------| | LOCAL_TERMINAL_TOKEN | 必填 | | LOCAL_TERMINAL_HOST / --host / --host= | 默认 0.0.0.0。只给本机连时再设 127.0.0.1 | | LOCAL_TERMINAL_PORT / --port / --port= | 默认 7788 |

监听地址打到 stderr:pty-session-mcp listening on http://0.0.0.0:8080。SIGINT / SIGTERM 会 dispose 全部会话后退出。

本仓库未发布的改动要用本地产物,不要只靠已发布的 npx:

LOCAL_TERMINAL_TOKEN=secret node libraries/local-terminal-mcp/dist/cli.js serve --host 0.0.0.0 --port 8080

错误码

| code | HTTP | 含义 | |------|------|------| | UNAUTHORIZED | 401 | token 缺失或错误 | | BAD_REQUEST | 400 | cwd 不可用、会话已退出、body 不合法等 | | NOT_FOUND | 404 | 会话或路由不存在 | | FORBIDDEN | 403 | cwd 不在 cwdAllowList 内 | | INTERNAL | 500 | 未预期错误 |

进程启动时没有 token:抛 TokenConfigError(LOCAL_TERMINAL_TOKEN is required and must be a non-empty string),CLI 退出码 1。

SDK 失败时抛 Error,message 为 CODE: ...。

常见问题

网关 connect error 111
daemon 只绑了 127.0.0.1,或前端 url 指向了浏览器到不了的地址。改 --host 0.0.0.0,url 用预览 / 网关域名。

cannot use cwd "/root/workspace": permission denied
daemon 不是 root,进不了 drwx------ 的 /root。容器里用 root 跑;或不传 cwd,默认 /home。

xterm 每个按键都像回车 / 错位
按键走了 HTTP write 且 submit 为 true。改用 subscribe().write。创建时传入 cols/rows,窗口变化要 resize。

npx pty-session-mcp 行为对不上源码
npx 拉的是 npm 上的版本。改完后先 rushx build,用 node dist/cli.js,或重新 publish。

输出缺了一截
环形缓冲默认 1 MiB,更早的数据在 read 的 dropped 里体现。加大 SessionManager({ bufferSize })。

以后加参数

在 src/core/types.ts 的对应 options 接口上加字段,并同步 src/core/schemas.ts。zod 使用 .passthrough(),新旧版本可以错开发布。

License

MIT