@scp-dev/mcp 0.1.3 → 0.1.5
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/dist/index.d.ts +8 -2
- package/dist/index.js +32 -6
- package/package.json +3 -3
- package/src/server.ts +37 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _scp_dev_core from '@scp-dev/core';
|
|
2
|
-
import { Claim } from '@scp-dev/core';
|
|
2
|
+
import { Claim, Wake } from '@scp-dev/core';
|
|
3
3
|
import { RelayClient } from '@scp-dev/broker';
|
|
4
4
|
|
|
5
5
|
declare const MCP_PROTOCOLS: readonly ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
@@ -25,6 +25,8 @@ interface RelayMcpOptions {
|
|
|
25
25
|
channels?: boolean;
|
|
26
26
|
/** the client was started with channels loaded (Claude Code does not declare the capability itself) */
|
|
27
27
|
launchedWithChannels?: boolean;
|
|
28
|
+
/** the wake the endpoint advertises while this shim pushes by channel, over `pi` for a pi client and `channels` for any other */
|
|
29
|
+
wake?: Wake;
|
|
28
30
|
version?: string;
|
|
29
31
|
/** starts a worker for relay_spawn, resolving with its name and its runner's pid once its harness started; without it there is no relay_spawn */
|
|
30
32
|
spawn?: (request: SpawnRequest) => Promise<{
|
|
@@ -289,7 +291,7 @@ declare const TOOLS: readonly [{
|
|
|
289
291
|
};
|
|
290
292
|
}, {
|
|
291
293
|
readonly name: "relay_join";
|
|
292
|
-
readonly description: "Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined).";
|
|
294
|
+
readonly description: "Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined); under links you below that agent, as if it had spawned you: you show as its child and reach only what a worker of it reaches.";
|
|
293
295
|
readonly inputSchema: {
|
|
294
296
|
readonly type: "object";
|
|
295
297
|
readonly properties: {
|
|
@@ -297,6 +299,10 @@ declare const TOOLS: readonly [{
|
|
|
297
299
|
readonly type: "string";
|
|
298
300
|
readonly maxLength: 63;
|
|
299
301
|
};
|
|
302
|
+
readonly under: {
|
|
303
|
+
readonly type: "string";
|
|
304
|
+
readonly maxLength: 128;
|
|
305
|
+
};
|
|
300
306
|
};
|
|
301
307
|
readonly additionalProperties: false;
|
|
302
308
|
};
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
deliveryText,
|
|
8
8
|
LIMITS,
|
|
9
9
|
PREAMBLE,
|
|
10
|
+
presenceText,
|
|
10
11
|
reachLines,
|
|
11
12
|
receiptLines,
|
|
12
13
|
RelayError,
|
|
@@ -160,8 +161,12 @@ var TOOLS = [
|
|
|
160
161
|
},
|
|
161
162
|
{
|
|
162
163
|
name: "relay_join",
|
|
163
|
-
description: "Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined).",
|
|
164
|
-
inputSchema: {
|
|
164
|
+
description: "Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined); under links you below that agent, as if it had spawned you: you show as its child and reach only what a worker of it reaches.",
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: { name: { type: "string", maxLength: 63 }, under: { type: "string", maxLength: 128 } },
|
|
168
|
+
additionalProperties: false
|
|
169
|
+
}
|
|
165
170
|
},
|
|
166
171
|
{
|
|
167
172
|
name: "relay_leave",
|
|
@@ -274,7 +279,11 @@ function heldText(held) {
|
|
|
274
279
|
return `held for review by your human: hold ${held.hold_id}; nothing was delivered`;
|
|
275
280
|
}
|
|
276
281
|
function sentContent(result) {
|
|
277
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
id: result.id,
|
|
284
|
+
thread: result.thread,
|
|
285
|
+
recipients: result.deliveries.map((d) => ({ address: d.address, ...d.state ? { state: d.state } : {}, ...d.wake ? { wake: d.wake } : {} }))
|
|
286
|
+
};
|
|
278
287
|
}
|
|
279
288
|
function redactedText(redacted) {
|
|
280
289
|
return redacted ? `; redacted ${redacted.n} span${redacted.n === 1 ? "" : "s"} (${redacted.kinds.join(", ")}) before delivery` : "";
|
|
@@ -400,6 +409,8 @@ async function runRelayMcp(options) {
|
|
|
400
409
|
let stopped = false;
|
|
401
410
|
const channelPushed = /* @__PURE__ */ new Set();
|
|
402
411
|
let channelLoop;
|
|
412
|
+
let wake;
|
|
413
|
+
let advertised;
|
|
403
414
|
const write = (message) => {
|
|
404
415
|
if (!stopped) options.output(`${JSON.stringify(message)}
|
|
405
416
|
`);
|
|
@@ -442,8 +453,17 @@ async function runRelayMcp(options) {
|
|
|
442
453
|
throw new Error("the relay MCP shim is stopping");
|
|
443
454
|
}
|
|
444
455
|
client = next;
|
|
456
|
+
advertise();
|
|
445
457
|
return next;
|
|
446
458
|
}
|
|
459
|
+
function advertise() {
|
|
460
|
+
if (!client || wake === void 0) return;
|
|
461
|
+
const via = wake ?? "none";
|
|
462
|
+
if (advertised?.client === client && advertised.via === via || via === "none" && !advertised) return;
|
|
463
|
+
advertised = { client, via };
|
|
464
|
+
client.request("wake", { via }).catch(() => {
|
|
465
|
+
});
|
|
466
|
+
}
|
|
447
467
|
function ensure() {
|
|
448
468
|
if (client) return Promise.resolve(client);
|
|
449
469
|
connecting ??= open().finally(() => connecting = void 0);
|
|
@@ -469,7 +489,7 @@ async function runRelayMcp(options) {
|
|
|
469
489
|
return [
|
|
470
490
|
`${e.address}${me && e.id === me.id ? " (you)" : ""}`,
|
|
471
491
|
e.harness ?? e.kind,
|
|
472
|
-
e
|
|
492
|
+
presenceText(e),
|
|
473
493
|
e.title ? `title ${quote(e.title)}` : "",
|
|
474
494
|
status,
|
|
475
495
|
cwd ? quote(cwd) : "",
|
|
@@ -634,17 +654,20 @@ async function runRelayMcp(options) {
|
|
|
634
654
|
}
|
|
635
655
|
case "relay_join": {
|
|
636
656
|
const name2 = str(args, "name");
|
|
657
|
+
const under = str(args, "under");
|
|
637
658
|
const joined = options.membership?.() ?? {};
|
|
638
659
|
const claim = "handle" in joined && joined.handle ? { ...options.claim, handle: joined.handle } : options.claim;
|
|
639
660
|
const next = await options.connect();
|
|
640
661
|
try {
|
|
641
|
-
const hello = await next.hello({ transient: true, claim, join: name2 ? { handle: name2 } : {} });
|
|
662
|
+
const hello = await next.hello({ transient: true, claim, join: { ...name2 ? { handle: name2 } : {}, ...under ? { under } : {} } });
|
|
642
663
|
me = hello.endpoint;
|
|
643
664
|
} finally {
|
|
644
665
|
next.close();
|
|
645
666
|
}
|
|
646
667
|
await ensure();
|
|
647
|
-
return text(
|
|
668
|
+
return text(
|
|
669
|
+
`joined the relay as ${me?.address}${me?.parent ? ` under ${me.parent}` : ""}: peers can message you, and new messages reach you after your tool calls`
|
|
670
|
+
);
|
|
648
671
|
}
|
|
649
672
|
case "relay_leave": {
|
|
650
673
|
const view = await call("leave");
|
|
@@ -808,6 +831,9 @@ ${wrapMessage(message)}`,
|
|
|
808
831
|
const capabilities = params.capabilities;
|
|
809
832
|
const channel = capabilities?.experimental?.["claude/channel"];
|
|
810
833
|
channelClient = typeof channel === "object" && channel !== null || options.launchedWithChannels === true;
|
|
834
|
+
const clientInfo = params.clientInfo;
|
|
835
|
+
wake = options.channels && channelClient ? options.wake ?? (clientInfo?.name === "pi" ? "pi" : "channels") : null;
|
|
836
|
+
advertise();
|
|
811
837
|
return respond({
|
|
812
838
|
protocolVersion,
|
|
813
839
|
capabilities: { tools: {}, ...options.channels ? { experimental: { "claude/channel": {} } } : {} },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scp-dev/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "The SCP stdio MCP server: gives an agent session the relay's tools.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://scp.dev",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"typecheck": "tsc -p ."
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@scp-dev/broker": "0.1.
|
|
33
|
-
"@scp-dev/core": "0.1.
|
|
32
|
+
"@scp-dev/broker": "0.1.5",
|
|
33
|
+
"@scp-dev/core": "0.1.4"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsup": "^8.5.1"
|
package/src/server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type SendResult,
|
|
19
19
|
type PeerView,
|
|
20
20
|
PREAMBLE,
|
|
21
|
+
presenceText,
|
|
21
22
|
reachLines,
|
|
22
23
|
receiptLines,
|
|
23
24
|
RelayError,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
shortId,
|
|
27
28
|
STATUS_STATES,
|
|
28
29
|
type ThreadMessage,
|
|
30
|
+
type Wake,
|
|
29
31
|
type WhyResult,
|
|
30
32
|
widenText,
|
|
31
33
|
wrapMessage,
|
|
@@ -65,6 +67,8 @@ export interface RelayMcpOptions {
|
|
|
65
67
|
channels?: boolean
|
|
66
68
|
/** the client was started with channels loaded (Claude Code does not declare the capability itself) */
|
|
67
69
|
launchedWithChannels?: boolean
|
|
70
|
+
/** the wake the endpoint advertises while this shim pushes by channel, over `pi` for a pi client and `channels` for any other */
|
|
71
|
+
wake?: Wake
|
|
68
72
|
version?: string
|
|
69
73
|
/** starts a worker for relay_spawn, resolving with its name and its runner's pid once its harness started; without it there is no relay_spawn */
|
|
70
74
|
spawn?: (request: SpawnRequest) => Promise<{ name: string; pid: number }>
|
|
@@ -232,8 +236,12 @@ export const TOOLS = [
|
|
|
232
236
|
{
|
|
233
237
|
name: "relay_join",
|
|
234
238
|
description:
|
|
235
|
-
"Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined).",
|
|
236
|
-
inputSchema: {
|
|
239
|
+
"Put this session on the relay, so peers can message it and new messages reach you after your tool calls; name sets your handle once (renaming you in place when you already joined); under links you below that agent, as if it had spawned you: you show as its child and reach only what a worker of it reaches.",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: { name: { type: "string", maxLength: 63 }, under: { type: "string", maxLength: 128 } },
|
|
243
|
+
additionalProperties: false,
|
|
244
|
+
},
|
|
237
245
|
},
|
|
238
246
|
{
|
|
239
247
|
name: "relay_leave",
|
|
@@ -362,7 +370,11 @@ function heldText(held: JudgeHeld): string {
|
|
|
362
370
|
|
|
363
371
|
/** a send's id, thread and recipients with their states, for a result's structuredContent */
|
|
364
372
|
function sentContent(result: SendResult): Record<string, unknown> {
|
|
365
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
id: result.id,
|
|
375
|
+
thread: result.thread,
|
|
376
|
+
recipients: result.deliveries.map((d) => ({ address: d.address, ...(d.state ? { state: d.state } : {}), ...(d.wake ? { wake: d.wake } : {}) })),
|
|
377
|
+
}
|
|
366
378
|
}
|
|
367
379
|
|
|
368
380
|
function redactedText(redacted: JudgeRedacted | undefined): string {
|
|
@@ -526,6 +538,9 @@ export async function runRelayMcp(options: RelayMcpOptions): Promise<void> {
|
|
|
526
538
|
/** seqs this shim pushed, so a push whose confirm was lost is not pushed again */
|
|
527
539
|
const channelPushed = new Set<number>()
|
|
528
540
|
let channelLoop: Promise<void> | undefined
|
|
541
|
+
/** what the endpoint advertises once initialize said whether the client takes channel pushes; null advertises nothing */
|
|
542
|
+
let wake: Wake | null | undefined
|
|
543
|
+
let advertised: { client: RelayClient; via: Wake } | undefined
|
|
529
544
|
|
|
530
545
|
const write = (message: Json) => {
|
|
531
546
|
if (!stopped) options.output(`${JSON.stringify(message)}\n`)
|
|
@@ -572,9 +587,19 @@ export async function runRelayMcp(options: RelayMcpOptions): Promise<void> {
|
|
|
572
587
|
throw new Error("the relay MCP shim is stopping")
|
|
573
588
|
}
|
|
574
589
|
client = next
|
|
590
|
+
advertise()
|
|
575
591
|
return next
|
|
576
592
|
}
|
|
577
593
|
|
|
594
|
+
/** tells the broker how a message wakes this session, once per connection and value, when both it and the client's capabilities are known */
|
|
595
|
+
function advertise() {
|
|
596
|
+
if (!client || wake === undefined) return
|
|
597
|
+
const via = wake ?? "none"
|
|
598
|
+
if ((advertised?.client === client && advertised.via === via) || (via === "none" && !advertised)) return
|
|
599
|
+
advertised = { client, via }
|
|
600
|
+
client.request("wake", { via }).catch(() => {})
|
|
601
|
+
}
|
|
602
|
+
|
|
578
603
|
function ensure(): Promise<RelayClient> {
|
|
579
604
|
if (client) return Promise.resolve(client)
|
|
580
605
|
connecting ??= open().finally(() => (connecting = undefined))
|
|
@@ -604,7 +629,7 @@ export async function runRelayMcp(options: RelayMcpOptions): Promise<void> {
|
|
|
604
629
|
return [
|
|
605
630
|
`${e.address}${me && e.id === me.id ? " (you)" : ""}`,
|
|
606
631
|
e.harness ?? e.kind,
|
|
607
|
-
e
|
|
632
|
+
presenceText(e),
|
|
608
633
|
e.title ? `title ${quote(e.title)}` : "",
|
|
609
634
|
status,
|
|
610
635
|
cwd ? quote(cwd) : "",
|
|
@@ -777,17 +802,20 @@ export async function runRelayMcp(options: RelayMcpOptions): Promise<void> {
|
|
|
777
802
|
}
|
|
778
803
|
case "relay_join": {
|
|
779
804
|
const name = str(args, "name")
|
|
805
|
+
const under = str(args, "under")
|
|
780
806
|
const joined = options.membership?.() ?? {}
|
|
781
807
|
const claim = "handle" in joined && joined.handle ? { ...options.claim, handle: joined.handle } : options.claim
|
|
782
808
|
const next = await options.connect()
|
|
783
809
|
try {
|
|
784
|
-
const hello = await next.hello({ transient: true, claim, join: name ? { handle: name } : {} })
|
|
810
|
+
const hello = await next.hello({ transient: true, claim, join: { ...(name ? { handle: name } : {}), ...(under ? { under } : {}) } })
|
|
785
811
|
me = hello.endpoint
|
|
786
812
|
} finally {
|
|
787
813
|
next.close()
|
|
788
814
|
}
|
|
789
815
|
await ensure()
|
|
790
|
-
return text(
|
|
816
|
+
return text(
|
|
817
|
+
`joined the relay as ${me?.address}${me?.parent ? ` under ${me.parent}` : ""}: peers can message you, and new messages reach you after your tool calls`,
|
|
818
|
+
)
|
|
791
819
|
}
|
|
792
820
|
case "relay_leave": {
|
|
793
821
|
const view = await call<EndpointView>("leave")
|
|
@@ -953,6 +981,9 @@ export async function runRelayMcp(options: RelayMcpOptions): Promise<void> {
|
|
|
953
981
|
const capabilities = params.capabilities as { experimental?: Record<string, unknown> } | undefined
|
|
954
982
|
const channel = capabilities?.experimental?.["claude/channel"]
|
|
955
983
|
channelClient = (typeof channel === "object" && channel !== null) || options.launchedWithChannels === true
|
|
984
|
+
const clientInfo = params.clientInfo as { name?: unknown } | undefined
|
|
985
|
+
wake = options.channels && channelClient ? (options.wake ?? (clientInfo?.name === "pi" ? "pi" : "channels")) : null
|
|
986
|
+
advertise()
|
|
956
987
|
return respond({
|
|
957
988
|
protocolVersion,
|
|
958
989
|
capabilities: { tools: {}, ...(options.channels ? { experimental: { "claude/channel": {} } } : {}) },
|