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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP connectors through ContextForge and a remote MCP endpoint for pi-roundtable",
5
5
  "type": "module",
6
6
  "license": "MIT",
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 { RemoteMcpMessages } from "./remote-mcp/messages.ts";
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 { REMOTE_MCP_MESSAGES, type RemoteMcpMessages } from "./messages.ts";
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: DISPATCH_TOOL,
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: RESULT_TOOL,
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
- dispatchDescription: string;
14
- resultDescription: string;
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
- "Returns { runId, sessionId } at once without waiting for the agent to finish; poll agent_result with the runId. " +
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
- "Poll a run started by agent_dispatch. Returns { status: 'working' | 'completed' | 'failed', text?, error? }.",
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 { type RemoteMcpMessages, remoteMcpMessages } from "./messages.ts";
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,