@klura/mcp 0.1.0 → 0.2.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 +1 -1
- package/index.js +62 -6
- package/package.json +2 -2
- package/tools.js +9 -1216
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Thin wrapper — each MCP `tools/call` dispatches to the corresponding klura run
|
|
|
46
46
|
MCP client (Claude Desktop, Cursor, …)
|
|
47
47
|
│ stdio transport, JSON-RPC
|
|
48
48
|
▼
|
|
49
|
-
klura
|
|
49
|
+
@klura/mcp (this package)
|
|
50
50
|
│ Node require('@klura/runtime')
|
|
51
51
|
▼
|
|
52
52
|
klura runtime → local daemon → Playwright
|
package/index.js
CHANGED
|
@@ -36,9 +36,19 @@ async function createKluraMcpServer() {
|
|
|
36
36
|
const skillMd = klura.getSkillMd()
|
|
37
37
|
.replace(/^---[\s\S]*?---\s*/, ''); // strip frontmatter
|
|
38
38
|
|
|
39
|
+
// Front-load a terse per-platform capability catalog so agents see what
|
|
40
|
+
// klura already knows BEFORE the first tool call. The list_platform_skills
|
|
41
|
+
// _hint only fires when the agent calls the tool, but the load-bearing
|
|
42
|
+
// failure mode (observed in field) is the agent skipping that call entirely
|
|
43
|
+
// and going straight to start_session for work an existing capability
|
|
44
|
+
// already covers. The deliberate principle break + always-save framing
|
|
45
|
+
// live in the rendered string itself (see getSavedSkillsSummaryMd).
|
|
46
|
+
const savedSkills = klura.getSavedSkillsSummaryMd();
|
|
47
|
+
const instructions = savedSkills ? `${skillMd}\n\n${savedSkills}` : skillMd;
|
|
48
|
+
|
|
39
49
|
const server = new Server(
|
|
40
50
|
{ name: '@klura/mcp', version: '0.1.0' },
|
|
41
|
-
{ capabilities: { tools: {}, resources: {} }, instructions
|
|
51
|
+
{ capabilities: { tools: {}, resources: {} }, instructions }
|
|
42
52
|
);
|
|
43
53
|
|
|
44
54
|
// -- Resources (on-demand reference docs) --
|
|
@@ -132,7 +142,7 @@ async function createKluraMcpServer() {
|
|
|
132
142
|
|
|
133
143
|
// -- Tool execution --
|
|
134
144
|
|
|
135
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
145
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
136
146
|
const { name, arguments: rawArgs } = request.params;
|
|
137
147
|
const tool = toolByName.get(name);
|
|
138
148
|
if (!tool) {
|
|
@@ -143,6 +153,50 @@ async function createKluraMcpServer() {
|
|
|
143
153
|
}
|
|
144
154
|
const args = coerceArgs(name, rawArgs);
|
|
145
155
|
|
|
156
|
+
// Progress notifications. When the client request carried
|
|
157
|
+
// `_meta.progressToken`, the SDK exposes it on `extra._meta` and gives us
|
|
158
|
+
// `extra.sendNotification` for sending `notifications/progress` bound to
|
|
159
|
+
// that token. Clients that honor this (Claude Desktop via MCP SDK with
|
|
160
|
+
// `resetTimeoutOnProgress: true`) reset their per-request timeout each
|
|
161
|
+
// time a progress arrives — turning a 4-minute hard deadline into a
|
|
162
|
+
// sliding window that survives long-running tools (end_drive on a real
|
|
163
|
+
// RE session does heavy synthesis + audit + handoff prose composition).
|
|
164
|
+
//
|
|
165
|
+
// Two emit paths:
|
|
166
|
+
// - Explicit phase boundaries inside the tool (e.g. endDrive's
|
|
167
|
+
// progress({stage: '...'}) calls). Names what's running so the user
|
|
168
|
+
// sees specific status, not just "still working".
|
|
169
|
+
// - 30s heartbeat for tools that don't emit explicit progress. Fires
|
|
170
|
+
// only when no explicit progress arrived in the last interval, so
|
|
171
|
+
// instrumented tools don't double-emit.
|
|
172
|
+
let progressCount = 0;
|
|
173
|
+
let lastProgressAt = Date.now();
|
|
174
|
+
let progress;
|
|
175
|
+
let heartbeat;
|
|
176
|
+
const progressToken = extra && extra._meta ? extra._meta.progressToken : undefined;
|
|
177
|
+
if (progressToken !== undefined && extra && typeof extra.sendNotification === 'function') {
|
|
178
|
+
progress = ({ stage, current, total } = {}) => {
|
|
179
|
+
progressCount += 1;
|
|
180
|
+
lastProgressAt = Date.now();
|
|
181
|
+
extra
|
|
182
|
+
.sendNotification({
|
|
183
|
+
method: 'notifications/progress',
|
|
184
|
+
params: {
|
|
185
|
+
progressToken,
|
|
186
|
+
progress: typeof current === 'number' ? current : progressCount,
|
|
187
|
+
...(typeof total === 'number' ? { total } : {}),
|
|
188
|
+
...(typeof stage === 'string' ? { message: stage } : {}),
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
.catch(() => { /* notification send failure is non-fatal */ });
|
|
192
|
+
};
|
|
193
|
+
heartbeat = setInterval(() => {
|
|
194
|
+
if (Date.now() - lastProgressAt >= 30000) {
|
|
195
|
+
progress({ stage: 'still working' });
|
|
196
|
+
}
|
|
197
|
+
}, 30000);
|
|
198
|
+
}
|
|
199
|
+
|
|
146
200
|
try {
|
|
147
201
|
// Phase admissibility — hard tool blocking per the session-phase
|
|
148
202
|
// state machine. Tools not in the current phase's allowedTools
|
|
@@ -203,11 +257,11 @@ async function createKluraMcpServer() {
|
|
|
203
257
|
});
|
|
204
258
|
}
|
|
205
259
|
|
|
206
|
-
let result = await tool.handler(args);
|
|
260
|
+
let result = await tool.handler(args, { progress });
|
|
207
261
|
|
|
208
262
|
// Inject sticky LIFT obligation reminder. Fires on every tool
|
|
209
263
|
// response between the first mutating perform_action and either a
|
|
210
|
-
// successful save_strategy or
|
|
264
|
+
// successful save_strategy or end_drive ok:true. Once-per-session
|
|
211
265
|
// semantics → no token-binding needed (see runtime/docs/gates.md
|
|
212
266
|
// §once-vs-many). klura.formatToolResult hoists the obligation
|
|
213
267
|
// message into a leading [klura obligation]: <message> text block
|
|
@@ -239,8 +293,8 @@ async function createKluraMcpServer() {
|
|
|
239
293
|
};
|
|
240
294
|
} catch (err) {
|
|
241
295
|
// Attach the LIFT obligation to error responses too. Without this,
|
|
242
|
-
// every save_strategy /
|
|
243
|
-
//
|
|
296
|
+
// every save_strategy / end_drive rejection drops the "MUST be
|
|
297
|
+
// end_drive" anchor exactly when the agent most needs it — agents
|
|
244
298
|
// reading just the bare error treat the failure as a one-off shape
|
|
245
299
|
// complaint and end the turn after the user-facing goal looks done.
|
|
246
300
|
let obligationLine = '';
|
|
@@ -271,6 +325,8 @@ async function createKluraMcpServer() {
|
|
|
271
325
|
content: [{ type: 'text', text: `${obligationLine}Error: ${msg}` }],
|
|
272
326
|
isError: true,
|
|
273
327
|
};
|
|
328
|
+
} finally {
|
|
329
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
274
330
|
}
|
|
275
331
|
});
|
|
276
332
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klura/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
34
|
-
"@klura/runtime": "^0.
|
|
34
|
+
"@klura/runtime": "^0.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^10.0.1",
|