@skillfm/local 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -10
- package/dist/guard/bin.js +0 -0
- package/dist/index.js +0 -0
- package/dist/mcp-stdio/api-client.d.ts +28 -0
- package/dist/mcp-stdio/api-client.d.ts.map +1 -0
- package/dist/mcp-stdio/api-client.js +123 -0
- package/dist/mcp-stdio/api-client.js.map +1 -0
- package/dist/mcp-stdio/bin.d.ts +3 -0
- package/dist/mcp-stdio/bin.d.ts.map +1 -0
- package/dist/mcp-stdio/bin.js +18 -0
- package/dist/mcp-stdio/bin.js.map +1 -0
- package/dist/mcp-stdio/config.d.ts +6 -0
- package/dist/mcp-stdio/config.d.ts.map +1 -0
- package/dist/mcp-stdio/config.js +26 -0
- package/dist/mcp-stdio/config.js.map +1 -0
- package/dist/mcp-stdio/render-flow.d.ts +53 -0
- package/dist/mcp-stdio/render-flow.d.ts.map +1 -0
- package/dist/mcp-stdio/render-flow.js +70 -0
- package/dist/mcp-stdio/render-flow.js.map +1 -0
- package/dist/mcp-stdio/request-context.d.ts +30 -0
- package/dist/mcp-stdio/request-context.d.ts.map +1 -0
- package/dist/mcp-stdio/request-context.js +78 -0
- package/dist/mcp-stdio/request-context.js.map +1 -0
- package/dist/mcp-stdio/server.d.ts +19 -0
- package/dist/mcp-stdio/server.d.ts.map +1 -0
- package/dist/mcp-stdio/server.js +651 -0
- package/dist/mcp-stdio/server.js.map +1 -0
- package/dist/skill-md/template.js +4 -4
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -14,11 +14,12 @@ This package sidesteps the problem: instead of asking the agent to install a new
|
|
|
14
14
|
npx -y @skillfm/local@latest start
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
> The package exposes
|
|
18
|
-
> npx invokes the file path directly
|
|
19
|
-
> `skillfm-local`
|
|
20
|
-
>
|
|
21
|
-
> `skillfm-
|
|
17
|
+
> The package exposes four bin aliases:
|
|
18
|
+
> - `local` — natural npx form (npx invokes the file path directly, never hits bash's `local` keyword).
|
|
19
|
+
> - `skillfm-local` — same HTTP sidecar entry; use this if `npm i -g @skillfm/local`, since bash would shadow a global `local`.
|
|
20
|
+
> - `skillfm-guard` — hook enforcement shim used by harness integrations.
|
|
21
|
+
> - `skillfm-mcp-stdio` — **stdio MCP server entry** (new in 2.1.0), for stdio-only agent runtimes like Claude Desktop. See [stdio MCP entry](#stdio-mcp-entry-new-in-210) below.
|
|
22
|
+
>
|
|
22
23
|
> Explicit equivalent: `npx -y --package @skillfm/local@latest skillfm-local start`.
|
|
23
24
|
|
|
24
25
|
Output is a single line of JSON the agent can parse:
|
|
@@ -80,14 +81,47 @@ All of this happens in a single conversation turn sequence. No MCP client reload
|
|
|
80
81
|
- **Zero runtime dependencies**: only Node built-ins (`http`, `fs`, `os`, `crypto`). The entire source is ~400 lines in one file. Audit it yourself: [sdk/skillfm-local/src/index.ts](https://github.com/ericm1018/skillfm/blob/main/sdk/skillfm-local/src/index.ts).
|
|
81
82
|
- **Kill switch**: `skillfm-local stop`, or just `kill <pid>`. The settings file is removed on exit.
|
|
82
83
|
|
|
83
|
-
##
|
|
84
|
+
## stdio MCP entry (new in 2.1.0)
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
Starting with 2.1.0, `@skillfm/local` also ships a stdio MCP server entry —
|
|
87
|
+
`skillfm-mcp-stdio` — so stdio-only agent runtimes (Claude Desktop, VS Code
|
|
88
|
+
Claude extension stdio mode, etc.) no longer need a separate npm package.
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
- **`@skillfm/mcp-server`** — proper MCP integration with schema-validated tools. Installed via `claude mcp add` or equivalent, activates after the next runtime cold start for a permanent upgrade.
|
|
90
|
+
Claude Desktop config example:
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
```jsonc
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"skillfm": {
|
|
96
|
+
"command": "npx",
|
|
97
|
+
"args": ["-y", "--package", "@skillfm/local@latest", "skillfm-mcp-stdio"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This exposes the same 7 tools (`list_skills`, `brain_run`, `continuation_abort`,
|
|
104
|
+
`charter_get`, `charter_ack`, `subscribe`, `my_status`) + 2 resources
|
|
105
|
+
(`skill-manifest`, `skill-catalog`) as the previously-separate
|
|
106
|
+
`@skillfm/mcp-server`. brain_key is read from `~/.skillfm/config.json` — the
|
|
107
|
+
same file written by `skillfm-local start`, so a single activation serves both
|
|
108
|
+
transports.
|
|
109
|
+
|
|
110
|
+
### Which entry to use
|
|
111
|
+
|
|
112
|
+
| Agent runtime | Entry |
|
|
113
|
+
|---|---|
|
|
114
|
+
| Claude Code / Cursor / Cline / OpenClaw / Aider (HTTP-capable) | `skillfm-local start` (HTTP sidecar, 127.0.0.1:19821) |
|
|
115
|
+
| Claude Desktop (stdio-only) | `skillfm-mcp-stdio` |
|
|
116
|
+
|
|
117
|
+
Both share `~/.skillfm/config.json` — activate once, either path works.
|
|
118
|
+
|
|
119
|
+
## Deprecation of @skillfm/mcp-server
|
|
120
|
+
|
|
121
|
+
`@skillfm/mcp-server@3.5.0` (released 2026-04-20) is the last standalone release.
|
|
122
|
+
From 2.1.0 onward use `@skillfm/local`'s `skillfm-mcp-stdio` bin instead.
|
|
123
|
+
`@skillfm/mcp-server` will receive a `npm deprecate` tag pointing to this
|
|
124
|
+
package once 2.1.0 has been stable for 48h.
|
|
91
125
|
|
|
92
126
|
## License
|
|
93
127
|
|
package/dist/guard/bin.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RequestContext } from './request-context.js';
|
|
2
|
+
interface RequestOptions {
|
|
3
|
+
method?: 'GET' | 'POST';
|
|
4
|
+
body?: Record<string, unknown>;
|
|
5
|
+
useAuth?: 'brain-key' | 'jwt' | 'none';
|
|
6
|
+
/**
|
|
7
|
+
* 可选:per-request 上下文。HTTP 多租户模式下必传。
|
|
8
|
+
* 未传时回退到模块级 config + 全局 conv-state(stdio 单租户 / tests)。
|
|
9
|
+
*/
|
|
10
|
+
ctx?: RequestContext;
|
|
11
|
+
}
|
|
12
|
+
export declare function getConversationState(): string | null;
|
|
13
|
+
export declare function setConversationState(s: string | null): void;
|
|
14
|
+
/**
|
|
15
|
+
* 把一份反馈结构(nudge / seed / proof)排入下一次出站请求。
|
|
16
|
+
* 一次性消费 — 注入后立即清空, 避免重复发送。
|
|
17
|
+
*/
|
|
18
|
+
export declare function pushConversationFeedback(payload: unknown): void;
|
|
19
|
+
export declare function _resetConversationState(): void;
|
|
20
|
+
export declare class ApiError extends Error {
|
|
21
|
+
status: number;
|
|
22
|
+
envelope: unknown;
|
|
23
|
+
body: unknown;
|
|
24
|
+
constructor(message: string, status: number, envelope?: unknown, body?: unknown);
|
|
25
|
+
}
|
|
26
|
+
export declare function apiCall<T = unknown>(path: string, options?: RequestOptions): Promise<T>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/api-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;IACvC;;;OAGG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAaD,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE/D;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAMD,qBAAa,QAAS,SAAQ,KAAK;IAGxB,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,OAAO;IACjB,IAAI,EAAE,OAAO;gBAHpB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,OAAc,EACxB,IAAI,GAAE,OAAc;CAK9B;AAED,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EACvC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAiGZ"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { config } from './config.js';
|
|
2
|
+
const MAX_RETRIES = 2;
|
|
3
|
+
const TIMEOUT_MS = 120_000; // Brain 管线可能耗时较长
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Module-level conv-state — 仅给 stdio 单租户/tests 使用
|
|
6
|
+
// HTTP 多租户模式下走 RequestContext.convState(ConvStateStore 按 brainKey 隔离)
|
|
7
|
+
// ============================================================================
|
|
8
|
+
let _convState = null;
|
|
9
|
+
let _convFeedback = null;
|
|
10
|
+
export function getConversationState() {
|
|
11
|
+
return _convState;
|
|
12
|
+
}
|
|
13
|
+
export function setConversationState(s) {
|
|
14
|
+
_convState = s;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 把一份反馈结构(nudge / seed / proof)排入下一次出站请求。
|
|
18
|
+
* 一次性消费 — 注入后立即清空, 避免重复发送。
|
|
19
|
+
*/
|
|
20
|
+
export function pushConversationFeedback(payload) {
|
|
21
|
+
_convFeedback = payload === null ? null : JSON.stringify(payload);
|
|
22
|
+
}
|
|
23
|
+
export function _resetConversationState() {
|
|
24
|
+
_convState = null;
|
|
25
|
+
_convFeedback = null;
|
|
26
|
+
}
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// ApiError — 把 server envelope 透传给调用方, 让 MCP 工具可以渲染 dialogue_flow
|
|
29
|
+
// ============================================================================
|
|
30
|
+
export class ApiError extends Error {
|
|
31
|
+
status;
|
|
32
|
+
envelope;
|
|
33
|
+
body;
|
|
34
|
+
constructor(message, status, envelope = null, body = null) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.status = status;
|
|
37
|
+
this.envelope = envelope;
|
|
38
|
+
this.body = body;
|
|
39
|
+
this.name = 'ApiError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function apiCall(path, options = {}) {
|
|
43
|
+
const { method = 'GET', body, useAuth = 'none', ctx } = options;
|
|
44
|
+
// 选源: ctx 优先 (HTTP 多租户), 否则 config + 模块 globals (stdio / tests)
|
|
45
|
+
const apiBaseUrl = ctx?.apiBaseUrl ?? config.apiBaseUrl;
|
|
46
|
+
const brainKey = ctx?.brainKey ?? config.brainKey;
|
|
47
|
+
const jwtToken = ctx?.jwtToken ?? config.token;
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
'User-Agent': 'SkillFMMCP/0.1.0',
|
|
51
|
+
};
|
|
52
|
+
if (useAuth === 'brain-key' && brainKey) {
|
|
53
|
+
headers['X-Brain-Key'] = brainKey;
|
|
54
|
+
}
|
|
55
|
+
else if (useAuth === 'jwt' && jwtToken) {
|
|
56
|
+
headers['Authorization'] = `Bearer ${jwtToken}`;
|
|
57
|
+
}
|
|
58
|
+
// 透传 conversation-state header(来自上一次 server 响应)
|
|
59
|
+
const convStateRead = ctx?.convState.get() ?? _convState;
|
|
60
|
+
if (convStateRead) {
|
|
61
|
+
headers['X-Conversation-State'] = convStateRead;
|
|
62
|
+
}
|
|
63
|
+
// feedback 一次性消费
|
|
64
|
+
if (ctx) {
|
|
65
|
+
const f = ctx.convState.consumeFeedback();
|
|
66
|
+
if (f)
|
|
67
|
+
headers['X-Conversation-Feedback'] = f;
|
|
68
|
+
}
|
|
69
|
+
else if (_convFeedback) {
|
|
70
|
+
headers['X-Conversation-Feedback'] = _convFeedback;
|
|
71
|
+
_convFeedback = null;
|
|
72
|
+
}
|
|
73
|
+
const url = `${apiBaseUrl}${path}`;
|
|
74
|
+
let lastError = null;
|
|
75
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch(url, {
|
|
80
|
+
method,
|
|
81
|
+
headers,
|
|
82
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
});
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
// 5xx 可重试
|
|
87
|
+
if (res.status >= 500 && attempt < MAX_RETRIES) {
|
|
88
|
+
const errData = await res.json().catch(() => ({ error: res.statusText }));
|
|
89
|
+
lastError = new Error(`API 错误 ${res.status}: ${errData.error || res.statusText}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// 不论成功还是软错误, 都要把 server 写回来的 conversation-state 拿走
|
|
93
|
+
const newState = res.headers.get('x-conversation-state');
|
|
94
|
+
if (newState) {
|
|
95
|
+
if (ctx)
|
|
96
|
+
ctx.convState.set(newState);
|
|
97
|
+
else
|
|
98
|
+
_convState = newState;
|
|
99
|
+
}
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const errBody = (await res.json().catch(() => ({ error: res.statusText })));
|
|
102
|
+
const message = errBody?.error || res.statusText;
|
|
103
|
+
// 把 envelope 透传给调用方 — Agent 工具可以渲染 dialogue_flow
|
|
104
|
+
throw new ApiError(`API 错误 ${res.status}: ${message}`, res.status, errBody?.envelope ?? null, errBody);
|
|
105
|
+
}
|
|
106
|
+
return res.json();
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
if (err.name === 'AbortError') {
|
|
111
|
+
throw new Error('请求超时');
|
|
112
|
+
}
|
|
113
|
+
// 网络错误可重试
|
|
114
|
+
if (attempt < MAX_RETRIES && !err.message?.startsWith('API 错误')) {
|
|
115
|
+
lastError = err;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw lastError || new Error('请求失败');
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/mcp-stdio/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/bin.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @skillfm/local — skillfm-mcp-stdio bin
|
|
3
|
+
//
|
|
4
|
+
// stdio MCP server 入口,供 stdio-only agent runtime(Claude Desktop 等)
|
|
5
|
+
// 作为 MCP transport 直接 fork:
|
|
6
|
+
// "command": "npx", "args": ["-y", "@skillfm/local", "skillfm-mcp-stdio"]
|
|
7
|
+
// 或 npx 直装后链接:
|
|
8
|
+
// "command": "skillfm-mcp-stdio"
|
|
9
|
+
//
|
|
10
|
+
// 等价路径(HTTP sidecar 模式)是 `skillfm-local start`,用户主入口统一在
|
|
11
|
+
// @skillfm/local 这一个 npm 包内,按 agent runtime 选 bin 即可。
|
|
12
|
+
import { main } from './server.js';
|
|
13
|
+
main().catch((err) => {
|
|
14
|
+
// stderr 不影响 stdio JSON-RPC 信道
|
|
15
|
+
process.stderr.write(`[skillfm-mcp-stdio] fatal: ${err?.message || err}\n`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=bin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/mcp-stdio/bin.ts"],"names":[],"mappings":";AACA,yCAAyC;AACzC,EAAE;AACF,mEAAmE;AACnE,4BAA4B;AAC5B,4EAA4E;AAC5E,eAAe;AACf,mCAAmC;AACnC,EAAE;AACF,wDAAwD;AACxD,sDAAsD;AAEtD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,+BAA+B;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8BAA8B,GAAG,EAAE,OAAO,IAAI,GAAG,IAAI,CACtD,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/config.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,MAAM;;;;CAOlB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// 配置管理 — 优先级:
|
|
2
|
+
// 1. 环境变量 (SKILLFM_BRAIN_KEY / SKILLFM_TOKEN / SKILLFM_API_URL)
|
|
3
|
+
// 2. ~/.skillfm/config.json (skillfm install 写入)
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
function loadFileConfig() {
|
|
8
|
+
try {
|
|
9
|
+
const path = join(homedir(), '.skillfm', 'config.json');
|
|
10
|
+
if (!existsSync(path))
|
|
11
|
+
return {};
|
|
12
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const file = loadFileConfig();
|
|
19
|
+
export const config = {
|
|
20
|
+
apiBaseUrl: process.env.SKILLFM_API_URL ||
|
|
21
|
+
file.apiBaseUrl ||
|
|
22
|
+
'https://api.skillfm.ai/api/v1',
|
|
23
|
+
brainKey: process.env.SKILLFM_BRAIN_KEY || file.agentToken || '',
|
|
24
|
+
token: process.env.SKILLFM_TOKEN || '',
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/mcp-stdio/config.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,kEAAkE;AAClE,mDAAmD;AAEnD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAOlC,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;QACxD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;AAE9B,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,UAAU,EACR,OAAO,CAAC,GAAG,CAAC,eAAe;QAC3B,IAAI,CAAC,UAAU;QACf,+BAA+B;IACjC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,UAAU,IAAI,EAAE;IAChE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE;CACvC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface LocalizedText {
|
|
2
|
+
zh?: string;
|
|
3
|
+
en?: string;
|
|
4
|
+
}
|
|
5
|
+
interface DialogueFlow {
|
|
6
|
+
flow_id: string;
|
|
7
|
+
current_turn: number;
|
|
8
|
+
total_turns: number;
|
|
9
|
+
this_turn: {
|
|
10
|
+
semantic_goal: string;
|
|
11
|
+
talk_points: string[];
|
|
12
|
+
end_with: string;
|
|
13
|
+
};
|
|
14
|
+
next_turn_hints: {
|
|
15
|
+
if_user_engages: string;
|
|
16
|
+
if_user_hesitates: string;
|
|
17
|
+
if_user_declines: string;
|
|
18
|
+
};
|
|
19
|
+
gentle_exit: {
|
|
20
|
+
semantic_goal: string;
|
|
21
|
+
talk_points: string[];
|
|
22
|
+
};
|
|
23
|
+
success_criterion: string;
|
|
24
|
+
}
|
|
25
|
+
interface EnhancedError {
|
|
26
|
+
code: string;
|
|
27
|
+
severity: string;
|
|
28
|
+
user_message?: LocalizedText;
|
|
29
|
+
agent_directive?: LocalizedText;
|
|
30
|
+
dialogue_flow?: DialogueFlow | null;
|
|
31
|
+
}
|
|
32
|
+
interface Envelope {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
error?: EnhancedError | null;
|
|
35
|
+
dialogue_flow?: DialogueFlow | null;
|
|
36
|
+
conversation_hints?: Array<{
|
|
37
|
+
text?: LocalizedText;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 把一份 dialogue_flow 渲染成对 Agent 友好的 markdown 块。
|
|
42
|
+
*
|
|
43
|
+
* 目标读者: Agent (Claude Code / Cursor 等), 不是终端用户。
|
|
44
|
+
* 因此用 [Guide / 引导剧本] 开头, 让 Agent 知道这是元信息而非用户话语。
|
|
45
|
+
*/
|
|
46
|
+
export declare function renderDialogueFlow(flow: DialogueFlow): string;
|
|
47
|
+
/**
|
|
48
|
+
* 渲染整个 envelope 的"软错误 + dialogue_flow"区段。
|
|
49
|
+
* 用在 MCP 工具的 catch 路径里。
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderEnvelopeError(envelope: Envelope | null): string | null;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=render-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-flow.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/render-flow.ts"],"names":[],"mappings":"AAOA,UAAU,aAAa;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACT,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,eAAe,EAAE;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,WAAW,EAAE;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IACF,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,eAAe,CAAC,EAAE,aAAa,CAAC;IAChC,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CACrC;AAED,UAAU,QAAQ;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;CACtD;AAMD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAuB7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAsB5E"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Render envelope.dialogue_flow + EnhancedError 成 Agent 可读文本
|
|
2
|
+
//
|
|
3
|
+
// PRD Book 3 §3.2: MCP 工具不翻译业务话术, 只把服务端 envelope 完整呈现给 Agent。
|
|
4
|
+
// Agent 自己根据 dialogue_flow.this_turn / next_turn_hints / gentle_exit 决定怎么说。
|
|
5
|
+
//
|
|
6
|
+
// 核心约束: 不在 MCP 这层注入额外文案, 只做"忠实搬运 + 结构化展示"。
|
|
7
|
+
function pickText(loc) {
|
|
8
|
+
return loc?.zh || loc?.en || '';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 把一份 dialogue_flow 渲染成对 Agent 友好的 markdown 块。
|
|
12
|
+
*
|
|
13
|
+
* 目标读者: Agent (Claude Code / Cursor 等), 不是终端用户。
|
|
14
|
+
* 因此用 [Guide / 引导剧本] 开头, 让 Agent 知道这是元信息而非用户话语。
|
|
15
|
+
*/
|
|
16
|
+
export function renderDialogueFlow(flow) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
lines.push(`[引导剧本 · ${flow.flow_id} · turn ${flow.current_turn}/${flow.total_turns}]`);
|
|
19
|
+
lines.push('');
|
|
20
|
+
lines.push(`本轮目标: ${flow.this_turn.semantic_goal}`);
|
|
21
|
+
if (flow.this_turn.talk_points?.length) {
|
|
22
|
+
lines.push('可以用的话题点:');
|
|
23
|
+
for (const p of flow.this_turn.talk_points)
|
|
24
|
+
lines.push(` - ${p}`);
|
|
25
|
+
}
|
|
26
|
+
lines.push(`本轮收尾方式: ${flow.this_turn.end_with}`);
|
|
27
|
+
lines.push('');
|
|
28
|
+
lines.push('下一轮根据 ta 的反应分支:');
|
|
29
|
+
lines.push(` · ta 接茬: ${flow.next_turn_hints.if_user_engages}`);
|
|
30
|
+
lines.push(` · ta 犹豫: ${flow.next_turn_hints.if_user_hesitates}`);
|
|
31
|
+
lines.push(` · ta 拒绝: ${flow.next_turn_hints.if_user_declines}`);
|
|
32
|
+
lines.push('');
|
|
33
|
+
lines.push(`温柔退路 (gentle_exit): ${flow.gentle_exit.semantic_goal}`);
|
|
34
|
+
if (flow.gentle_exit.talk_points?.length) {
|
|
35
|
+
for (const p of flow.gentle_exit.talk_points)
|
|
36
|
+
lines.push(` - ${p}`);
|
|
37
|
+
}
|
|
38
|
+
lines.push('');
|
|
39
|
+
lines.push(`本轮成功判定: ${flow.success_criterion}`);
|
|
40
|
+
return lines.join('\n');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 渲染整个 envelope 的"软错误 + dialogue_flow"区段。
|
|
44
|
+
* 用在 MCP 工具的 catch 路径里。
|
|
45
|
+
*/
|
|
46
|
+
export function renderEnvelopeError(envelope) {
|
|
47
|
+
if (!envelope || !envelope.error)
|
|
48
|
+
return null;
|
|
49
|
+
const err = envelope.error;
|
|
50
|
+
const blocks = [];
|
|
51
|
+
const userMsg = pickText(err.user_message);
|
|
52
|
+
if (userMsg)
|
|
53
|
+
blocks.push(userMsg);
|
|
54
|
+
const directive = pickText(err.agent_directive);
|
|
55
|
+
if (directive)
|
|
56
|
+
blocks.push(`[agent 心法] ${directive}`);
|
|
57
|
+
const flow = envelope.dialogue_flow || err.dialogue_flow;
|
|
58
|
+
if (flow)
|
|
59
|
+
blocks.push(renderDialogueFlow(flow));
|
|
60
|
+
if (envelope.conversation_hints?.length) {
|
|
61
|
+
blocks.push('—');
|
|
62
|
+
for (const h of envelope.conversation_hints) {
|
|
63
|
+
const t = pickText(h.text);
|
|
64
|
+
if (t)
|
|
65
|
+
blocks.push(`[hint] ${t}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return blocks.join('\n\n');
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=render-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-flow.js","sourceRoot":"","sources":["../../src/mcp-stdio/render-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface RequestContext {
|
|
2
|
+
apiBaseUrl: string;
|
|
3
|
+
brainKey: string;
|
|
4
|
+
jwtToken: string;
|
|
5
|
+
/** 同一个 brainKey 下的 conversation-state 读写器(HTTP 模式下从 store 借用,stdio 下可以是全局单例) */
|
|
6
|
+
convState: ConvStateHandle;
|
|
7
|
+
}
|
|
8
|
+
export interface ConvStateHandle {
|
|
9
|
+
get(): string | null;
|
|
10
|
+
set(v: string | null): void;
|
|
11
|
+
getFeedback(): string | null;
|
|
12
|
+
consumeFeedback(): string | null;
|
|
13
|
+
pushFeedback(v: unknown): void;
|
|
14
|
+
}
|
|
15
|
+
export declare class ConvStateStore {
|
|
16
|
+
private map;
|
|
17
|
+
getHandle(brainKey: string): ConvStateHandle;
|
|
18
|
+
private getEntry;
|
|
19
|
+
private touch;
|
|
20
|
+
private evictIfNeeded;
|
|
21
|
+
/** 测试辅助 */
|
|
22
|
+
_clear(): void;
|
|
23
|
+
_size(): number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 全局单例 store — stdio 模式/tests 也会用到,HTTP 模式的 app 实例也直接用它。
|
|
27
|
+
* 不同 brainKey 天然隔离。
|
|
28
|
+
*/
|
|
29
|
+
export declare const globalConvStore: ConvStateStore;
|
|
30
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/request-context.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,IAAI,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,GAAG,CAAgC;IAE3C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe;IAwB5C,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,aAAa;IAWrB,WAAW;IACX,MAAM,IAAI,IAAI;IAId,KAAK,IAAI,MAAM;CAGhB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// RequestContext — 每请求独立的客户端上下文,HTTP Streamable 模式下使用。
|
|
2
|
+
//
|
|
3
|
+
// 为什么需要这个?
|
|
4
|
+
// stdio 模式是单租户进程,全局 config + 全局 conv-state 没问题。
|
|
5
|
+
// HTTP 模式是多租户(多个 agent 带各自的 Bearer 打到同一个 process),
|
|
6
|
+
// 必须按 brainKey 隔离出站状态,避免 conv-state bleed across agents。
|
|
7
|
+
//
|
|
8
|
+
// 设计:
|
|
9
|
+
// - RequestContext 不可变 (per-request frozen)
|
|
10
|
+
// - ConvStateStore 是 brainKey → {state, feedback} 的 Map
|
|
11
|
+
// - LRU-ish cap(512 条)防止内存无界增长;真出问题再换 Redis
|
|
12
|
+
//
|
|
13
|
+
// BYOK 红线 (Principle 24): 这里永不读/存任何 LLM key, 只存平台自己的 brainKey + JWT。
|
|
14
|
+
const MAX_ENTRIES = 512;
|
|
15
|
+
export class ConvStateStore {
|
|
16
|
+
map = new Map();
|
|
17
|
+
getHandle(brainKey) {
|
|
18
|
+
const store = this;
|
|
19
|
+
return {
|
|
20
|
+
get: () => store.getEntry(brainKey).state,
|
|
21
|
+
set: (v) => {
|
|
22
|
+
const e = store.getEntry(brainKey);
|
|
23
|
+
e.state = v;
|
|
24
|
+
store.touch(brainKey, e);
|
|
25
|
+
},
|
|
26
|
+
getFeedback: () => store.getEntry(brainKey).feedback,
|
|
27
|
+
consumeFeedback: () => {
|
|
28
|
+
const e = store.getEntry(brainKey);
|
|
29
|
+
const f = e.feedback;
|
|
30
|
+
e.feedback = null;
|
|
31
|
+
return f;
|
|
32
|
+
},
|
|
33
|
+
pushFeedback: (v) => {
|
|
34
|
+
const e = store.getEntry(brainKey);
|
|
35
|
+
e.feedback = v === null ? null : JSON.stringify(v);
|
|
36
|
+
store.touch(brainKey, e);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
getEntry(brainKey) {
|
|
41
|
+
let e = this.map.get(brainKey);
|
|
42
|
+
if (!e) {
|
|
43
|
+
e = { state: null, feedback: null };
|
|
44
|
+
this.map.set(brainKey, e);
|
|
45
|
+
this.evictIfNeeded();
|
|
46
|
+
}
|
|
47
|
+
return e;
|
|
48
|
+
}
|
|
49
|
+
touch(brainKey, e) {
|
|
50
|
+
// LRU: delete + re-set 让它移到 Map 末尾
|
|
51
|
+
this.map.delete(brainKey);
|
|
52
|
+
this.map.set(brainKey, e);
|
|
53
|
+
}
|
|
54
|
+
evictIfNeeded() {
|
|
55
|
+
while (this.map.size > MAX_ENTRIES) {
|
|
56
|
+
const oldest = this.map.keys().next().value;
|
|
57
|
+
if (oldest !== undefined) {
|
|
58
|
+
this.map.delete(oldest);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** 测试辅助 */
|
|
66
|
+
_clear() {
|
|
67
|
+
this.map.clear();
|
|
68
|
+
}
|
|
69
|
+
_size() {
|
|
70
|
+
return this.map.size;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 全局单例 store — stdio 模式/tests 也会用到,HTTP 模式的 app 实例也直接用它。
|
|
75
|
+
* 不同 brainKey 天然隔离。
|
|
76
|
+
*/
|
|
77
|
+
export const globalConvStore = new ConvStateStore();
|
|
78
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/mcp-stdio/request-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { type RequestContext } from './request-context.js';
|
|
4
|
+
type ToolContextProvider = (extra: {
|
|
5
|
+
authInfo?: {
|
|
6
|
+
token?: string;
|
|
7
|
+
};
|
|
8
|
+
} | undefined) => RequestContext;
|
|
9
|
+
/**
|
|
10
|
+
* 构造一个注册了所有工具的 McpServer。
|
|
11
|
+
* 工具回调在运行时调用 getCtx(extra) 拿到 per-request RequestContext。
|
|
12
|
+
*/
|
|
13
|
+
declare function buildMcpServer(getCtx: ToolContextProvider): McpServer;
|
|
14
|
+
export { buildMcpServer };
|
|
15
|
+
export { globalConvStore } from './request-context.js';
|
|
16
|
+
export type { RequestContext } from './request-context.js';
|
|
17
|
+
export type { ToolContextProvider };
|
|
18
|
+
export declare function main(): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp-stdio/server.ts"],"names":[],"mappings":";AAoBA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAOtF,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQ5E,KAAK,mBAAmB,GAAG,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,KAAK,cAAc,CAAC;AAEpG;;;GAGG;AACH,iBAAS,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAqkB9D;AA4BD,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAU1C"}
|