pi-roundtable-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/CHANGELOG.md +10 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/index.ts +4 -1
- package/src/remote-mcp/mcp-gateway.ts +14 -7
- package/src/remote-mcp/messages.ts +19 -6
- package/src/remote-mcp/remote-mcp-plugin.ts +28 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-10-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `remoteMcp({ toolNames })`: the names of the two tools at `/mcp/personal`, for agents that are already set up with other names. The default descriptions name each other with the chosen names. A name that is not letters, digits, `_` or `-` (up to 64), or two equal names, is refused when the plugin is created.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `RemoteMcpMessages.dispatchDescription` and `resultDescription` are now functions that receive the tool names, so a description can refer to the other tool by its real name. A host that set either as a string gives `() => "..."` instead.
|
|
17
|
+
|
|
8
18
|
## [0.1.0] - 2026-10-01
|
|
9
19
|
|
|
10
20
|
First release: two plugins for pi-roundtable 0.4, extracted from a private host.
|
package/README.md
CHANGED
|
@@ -153,7 +153,8 @@ The owner manages connectors on Discord with `/<root> connector`, where `<root>`
|
|
|
153
153
|
| `publicUrl` | `string` | required | The HTTPS address that reaches the host's `public` listener. Granted-channel URLs are built on its origin |
|
|
154
154
|
| `persona` | `string` | a short neutral prompt | The system prompt of the default `remote` conversations |
|
|
155
155
|
| `answer`, `claim` | see [below](#when-the-host-runs-the-conversations-itself) | the core's runtime | Give both, or neither |
|
|
156
|
-
| `messages` | `Partial<RemoteMcpMessages>` | English | The relay note, the tool descriptions, and the Discord text, in your wording |
|
|
156
|
+
| `messages` | `Partial<RemoteMcpMessages>` | English | The relay note, the tool descriptions, and the Discord text, in your wording. `dispatchDescription` and `resultDescription` are functions that receive the tool names |
|
|
157
|
+
| `toolNames` | `{ dispatch?: string; result?: string }` | `agent_dispatch`, `agent_result` | The names of the two tools at `/mcp/personal`, for agents that are already set up with other names. The default descriptions follow them |
|
|
157
158
|
|
|
158
159
|
The plugin serves two endpoints on the host's `public` listener:
|
|
159
160
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -13,7 +13,10 @@ export type {
|
|
|
13
13
|
export { CONNECTORS, mcpConnectors } from "./connectors/connectors-plugin.ts";
|
|
14
14
|
export type { UpstreamAuth } from "./connectors/contextforge.ts";
|
|
15
15
|
export type { ConnectorMessages } from "./connectors/messages.ts";
|
|
16
|
-
export type {
|
|
16
|
+
export type {
|
|
17
|
+
RemoteMcpMessages,
|
|
18
|
+
RemoteToolNames,
|
|
19
|
+
} from "./remote-mcp/messages.ts";
|
|
17
20
|
export type { RemoteClaimHooks } from "./remote-mcp/remote-claim.ts";
|
|
18
21
|
export type {
|
|
19
22
|
DefaultConversationOptions,
|
|
@@ -23,14 +23,17 @@ import {
|
|
|
23
23
|
hashChannelToken,
|
|
24
24
|
} from "./channel-grants.ts";
|
|
25
25
|
import { runGrantedTool } from "./channel-tools.ts";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_TOOL_NAMES,
|
|
28
|
+
REMOTE_MCP_MESSAGES,
|
|
29
|
+
type RemoteMcpMessages,
|
|
30
|
+
type RemoteToolNames,
|
|
31
|
+
} from "./messages.ts";
|
|
27
32
|
import { type RemoteAgent, RemoteAgentError } from "./remote-agent.ts";
|
|
28
33
|
|
|
29
34
|
/** Requests may carry uploads; anything larger is refused before it is parsed. */
|
|
30
35
|
const MAX_BODY_BYTES = 12 * 1024 * 1024;
|
|
31
36
|
|
|
32
|
-
export const DISPATCH_TOOL = "agent_dispatch";
|
|
33
|
-
export const RESULT_TOOL = "agent_result";
|
|
34
37
|
const LIST_CHANNELS_TOOL = "discord_list_authorized_channels";
|
|
35
38
|
|
|
36
39
|
export interface McpGatewayOptions {
|
|
@@ -42,6 +45,8 @@ export interface McpGatewayOptions {
|
|
|
42
45
|
executor(): ChannelExecutor | undefined;
|
|
43
46
|
logger: Logger;
|
|
44
47
|
messages?: RemoteMcpMessages;
|
|
48
|
+
/** The names of the dispatch and result tools; `agent_dispatch` and `agent_result` by default. */
|
|
49
|
+
toolNames?: RemoteToolNames;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
interface ToolSpec {
|
|
@@ -73,11 +78,13 @@ const ResultInput = Type.Object(
|
|
|
73
78
|
export class McpGateway {
|
|
74
79
|
readonly #options: McpGatewayOptions;
|
|
75
80
|
readonly #text: RemoteMcpMessages;
|
|
81
|
+
readonly #tools: RemoteToolNames;
|
|
76
82
|
readonly #dispatchInput: TSchema;
|
|
77
83
|
|
|
78
84
|
constructor(options: McpGatewayOptions) {
|
|
79
85
|
this.#options = options;
|
|
80
86
|
this.#text = options.messages ?? REMOTE_MCP_MESSAGES;
|
|
87
|
+
this.#tools = options.toolNames ?? DEFAULT_TOOL_NAMES;
|
|
81
88
|
this.#dispatchInput = Type.Object(
|
|
82
89
|
{
|
|
83
90
|
message: Type.String({ minLength: 1 }),
|
|
@@ -172,8 +179,8 @@ export class McpGateway {
|
|
|
172
179
|
return [
|
|
173
180
|
{
|
|
174
181
|
tool: {
|
|
175
|
-
name:
|
|
176
|
-
description: this.#text.dispatchDescription,
|
|
182
|
+
name: this.#tools.dispatch,
|
|
183
|
+
description: this.#text.dispatchDescription(this.#tools),
|
|
177
184
|
inputSchema: jsonSchema(this.#dispatchInput),
|
|
178
185
|
},
|
|
179
186
|
call: async (args) => {
|
|
@@ -187,8 +194,8 @@ export class McpGateway {
|
|
|
187
194
|
},
|
|
188
195
|
{
|
|
189
196
|
tool: {
|
|
190
|
-
name:
|
|
191
|
-
description: this.#text.resultDescription,
|
|
197
|
+
name: this.#tools.result,
|
|
198
|
+
description: this.#text.resultDescription(this.#tools),
|
|
192
199
|
inputSchema: jsonSchema(ResultInput),
|
|
193
200
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
194
201
|
},
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/** The names of the two tools `/mcp/personal` offers. */
|
|
2
|
+
export interface RemoteToolNames {
|
|
3
|
+
dispatch: string;
|
|
4
|
+
result: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_TOOL_NAMES: RemoteToolNames = {
|
|
8
|
+
dispatch: "agent_dispatch",
|
|
9
|
+
result: "agent_result",
|
|
10
|
+
};
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* The text of the remote MCP plugin: what outside agents read in tool descriptions and errors,
|
|
3
14
|
* and what the owner reads in the Discord commands.
|
|
@@ -10,8 +21,10 @@ export interface RemoteMcpMessages {
|
|
|
10
21
|
/** Sent for a granted tool call that failed in a way the agent must not retry. */
|
|
11
22
|
operationUnfinished: string;
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
/** The description of the dispatch tool; `tools` holds the names the host chose. */
|
|
25
|
+
dispatchDescription(tools: RemoteToolNames): string;
|
|
26
|
+
/** The description of the result tool. */
|
|
27
|
+
resultDescription(tools: RemoteToolNames): string;
|
|
15
28
|
sessionIdDescription: string;
|
|
16
29
|
listChannelsDescription: string;
|
|
17
30
|
/** Appended to every granted channel tool's description. */
|
|
@@ -87,12 +100,12 @@ export const REMOTE_MCP_MESSAGES: RemoteMcpMessages = {
|
|
|
87
100
|
operationUnfinished:
|
|
88
101
|
"The operation did not complete. Check the audit log of the grants on Discord; do not retry automatically.",
|
|
89
102
|
|
|
90
|
-
dispatchDescription:
|
|
103
|
+
dispatchDescription: (tools) =>
|
|
91
104
|
"Start or continue a conversation turn with the owner's personal agent, on the owner's behalf. " +
|
|
92
|
-
|
|
105
|
+
`Returns { runId, sessionId } at once without waiting for the agent to finish; poll ${tools.result} with the runId. ` +
|
|
93
106
|
"Omit sessionId to start a new conversation; pass a returned sessionId to continue it.",
|
|
94
|
-
resultDescription:
|
|
95
|
-
|
|
107
|
+
resultDescription: (tools) =>
|
|
108
|
+
`Poll a run started by ${tools.dispatch}. Returns { status: 'working' | 'completed' | 'failed', text?, error? }.`,
|
|
96
109
|
sessionIdDescription:
|
|
97
110
|
"A sessionId returned earlier; omit it to start a new conversation",
|
|
98
111
|
listChannelsDescription:
|
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
} from "./default-conversation.ts";
|
|
19
19
|
import { McpGateway } from "./mcp-gateway.ts";
|
|
20
20
|
import { mcpGrantCommands } from "./mcp-grant-commands.ts";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_TOOL_NAMES,
|
|
23
|
+
type RemoteMcpMessages,
|
|
24
|
+
type RemoteToolNames,
|
|
25
|
+
remoteMcpMessages,
|
|
26
|
+
} from "./messages.ts";
|
|
22
27
|
import { RemoteAgent } from "./remote-agent.ts";
|
|
23
28
|
import { type RemoteClaimHooks, remoteClaim } from "./remote-claim.ts";
|
|
24
29
|
import { RemoteSessionStore } from "./remote-session-store.ts";
|
|
@@ -37,6 +42,11 @@ interface RemoteMcpBaseOptions {
|
|
|
37
42
|
publicUrl: string;
|
|
38
43
|
/** The Discord text, the relay note, and the tool descriptions in your wording; English by default. */
|
|
39
44
|
messages?: Partial<RemoteMcpMessages>;
|
|
45
|
+
/**
|
|
46
|
+
* The names of the two tools at `/mcp/personal`, for agents already set up with other names;
|
|
47
|
+
* `agent_dispatch` and `agent_result` by default. The default descriptions follow the names.
|
|
48
|
+
*/
|
|
49
|
+
toolNames?: Partial<RemoteToolNames>;
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
/** Remote turns run on the core's runtime: nothing more to give. */
|
|
@@ -63,6 +73,21 @@ export interface HostConversationOptions {
|
|
|
63
73
|
export type RemoteMcpOptions = RemoteMcpBaseOptions &
|
|
64
74
|
(DefaultConversationOptions | HostConversationOptions);
|
|
65
75
|
|
|
76
|
+
const TOOL_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
77
|
+
|
|
78
|
+
/** The tool names with the host's overrides laid over the defaults; refuses names a client cannot use. */
|
|
79
|
+
function toolNamesOf(options: RemoteMcpOptions): RemoteToolNames {
|
|
80
|
+
const names = { ...DEFAULT_TOOL_NAMES, ...options.toolNames };
|
|
81
|
+
for (const name of Object.values(names))
|
|
82
|
+
if (!TOOL_NAME.test(name))
|
|
83
|
+
throw new ConfigError(
|
|
84
|
+
`remote-mcp: toolNames: "${name}" is not a tool name (letters, digits, _ and -, up to 64)`,
|
|
85
|
+
);
|
|
86
|
+
if (names.dispatch === names.result)
|
|
87
|
+
throw new ConfigError("remote-mcp: toolNames: the two names must differ");
|
|
88
|
+
return names;
|
|
89
|
+
}
|
|
90
|
+
|
|
66
91
|
function checkOptions(options: RemoteMcpOptions): void {
|
|
67
92
|
if (!options.dispatchToken)
|
|
68
93
|
throw new ConfigError("remote-mcp: dispatchToken is empty");
|
|
@@ -83,6 +108,7 @@ function checkOptions(options: RemoteMcpOptions): void {
|
|
|
83
108
|
export function remoteMcp(options: RemoteMcpOptions): RoundtablePlugin {
|
|
84
109
|
checkOptions(options);
|
|
85
110
|
const text = remoteMcpMessages(options.messages);
|
|
111
|
+
const toolNames = toolNamesOf(options);
|
|
86
112
|
return definePlugin({
|
|
87
113
|
name: "remote-mcp",
|
|
88
114
|
requires: [DISCORD],
|
|
@@ -105,6 +131,7 @@ export function remoteMcp(options: RemoteMcpOptions): RoundtablePlugin {
|
|
|
105
131
|
executor: () => discord.connection.channelExecutor(),
|
|
106
132
|
logger,
|
|
107
133
|
messages: text,
|
|
134
|
+
toolNames,
|
|
108
135
|
});
|
|
109
136
|
const sweeper = new RemoteSessionSweeper({
|
|
110
137
|
sessions,
|