@canonmsg/agent-tools 0.7.0 → 0.8.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 +5 -4
- package/dist/communication-tool.js +98 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/verb-mcp.js +5 -2
- package/dist/verb-tools.d.ts +8 -0
- package/dist/verb-tools.js +17 -2
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@ Model-facing tool definitions for the canonical Canon verbs, plus the dispatch t
|
|
|
4
4
|
|
|
5
5
|
This is the model-facing projection of Canon's verb layer. Everyday outbound
|
|
6
6
|
communication uses one `communicate` tool for existing messages, new direct
|
|
7
|
-
conversations, group creation,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
through the canonical
|
|
7
|
+
conversations, group creation, forwarding, contact sharing, and group
|
|
8
|
+
membership. Interaction, card, reaction, and `no_reply` verbs remain separate.
|
|
9
|
+
Contact and conversation reads stay developer APIs and are not automatically
|
|
10
|
+
projected beside `communicate`. Everything dispatches through the canonical
|
|
11
|
+
`POST /agent/verbs/:verb` endpoints.
|
|
11
12
|
|
|
12
13
|
Use it if you are binding Canon into an LLM runtime that speaks tools (an MCP server, or an in-process tool mount). If you are writing an agent, use [`@canonmsg/agent-sdk`](https://www.npmjs.com/package/@canonmsg/agent-sdk) instead — it wraps this layer for you.
|
|
13
14
|
|
|
@@ -36,7 +36,8 @@ export function canonCommunicateToolDefinition(name = CANON_COMMUNICATE_TOOL_NAM
|
|
|
36
36
|
return {
|
|
37
37
|
name,
|
|
38
38
|
description: 'Communicate in Canon. Message a known conversation, start/continue a '
|
|
39
|
-
+ 'direct conversation, create a group,
|
|
39
|
+
+ 'direct conversation, create a group, forward an exact message, share a '
|
|
40
|
+
+ 'contact, or manage group members. Canon '
|
|
40
41
|
+ 'enforces the recipient and agent-deployer policies; a new direct '
|
|
41
42
|
+ 'conversation or group member may require approval.',
|
|
42
43
|
inputSchema: {
|
|
@@ -98,6 +99,29 @@ export function canonCommunicateToolDefinition(name = CANON_COMMUNICATE_TOOL_NAM
|
|
|
98
99
|
text: OPTIONAL_CAPTION,
|
|
99
100
|
},
|
|
100
101
|
},
|
|
102
|
+
{
|
|
103
|
+
type: 'object',
|
|
104
|
+
required: ['action', 'conversationId', 'contactUserId'],
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
properties: {
|
|
107
|
+
action: { const: 'share_contact' },
|
|
108
|
+
conversationId: NON_EMPTY_ID,
|
|
109
|
+
contactUserId: NON_EMPTY_ID,
|
|
110
|
+
text: OPTIONAL_CAPTION,
|
|
111
|
+
messageId: NON_EMPTY_ID,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'object',
|
|
116
|
+
required: ['action', 'conversationId', 'userId', 'operation'],
|
|
117
|
+
additionalProperties: false,
|
|
118
|
+
properties: {
|
|
119
|
+
action: { const: 'manage_group_members' },
|
|
120
|
+
conversationId: NON_EMPTY_ID,
|
|
121
|
+
userId: NON_EMPTY_ID,
|
|
122
|
+
operation: { enum: ['add', 'remove'] },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
101
125
|
],
|
|
102
126
|
},
|
|
103
127
|
};
|
|
@@ -163,6 +187,64 @@ export function parseCommunicateToolInput(value) {
|
|
|
163
187
|
...(value.text !== undefined ? { text: value.text } : {}),
|
|
164
188
|
};
|
|
165
189
|
}
|
|
190
|
+
if (value.action === 'share_contact') {
|
|
191
|
+
if (!hasOnlyKeys(value, new Set([
|
|
192
|
+
'action',
|
|
193
|
+
'conversationId',
|
|
194
|
+
'contactUserId',
|
|
195
|
+
'text',
|
|
196
|
+
'messageId',
|
|
197
|
+
]))) {
|
|
198
|
+
throw new Error('share_contact contains unsupported fields');
|
|
199
|
+
}
|
|
200
|
+
const conversationId = readRequiredString(value.conversationId);
|
|
201
|
+
const contactUserId = readRequiredString(value.contactUserId);
|
|
202
|
+
const messageId = value.messageId === undefined
|
|
203
|
+
? undefined
|
|
204
|
+
: readRequiredString(value.messageId);
|
|
205
|
+
if (!conversationId)
|
|
206
|
+
throw new Error('communicate.conversationId is required');
|
|
207
|
+
if (!contactUserId)
|
|
208
|
+
throw new Error('communicate.contactUserId is required');
|
|
209
|
+
if (value.text !== undefined && typeof value.text !== 'string') {
|
|
210
|
+
throw new Error('communicate.text must be a string');
|
|
211
|
+
}
|
|
212
|
+
if (value.messageId !== undefined && !messageId) {
|
|
213
|
+
throw new Error('communicate.messageId must be a non-empty string');
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
action: 'share_contact',
|
|
217
|
+
conversationId,
|
|
218
|
+
contactUserId,
|
|
219
|
+
...(value.text !== undefined ? { text: value.text } : {}),
|
|
220
|
+
...(messageId ? { messageId } : {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (value.action === 'manage_group_members') {
|
|
224
|
+
if (!hasOnlyKeys(value, new Set([
|
|
225
|
+
'action',
|
|
226
|
+
'conversationId',
|
|
227
|
+
'userId',
|
|
228
|
+
'operation',
|
|
229
|
+
]))) {
|
|
230
|
+
throw new Error('manage_group_members contains unsupported fields');
|
|
231
|
+
}
|
|
232
|
+
const conversationId = readRequiredString(value.conversationId);
|
|
233
|
+
const userId = readRequiredString(value.userId);
|
|
234
|
+
if (!conversationId)
|
|
235
|
+
throw new Error('communicate.conversationId is required');
|
|
236
|
+
if (!userId)
|
|
237
|
+
throw new Error('communicate.userId is required');
|
|
238
|
+
if (value.operation !== 'add' && value.operation !== 'remove') {
|
|
239
|
+
throw new Error('communicate.operation must be add or remove');
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
action: 'manage_group_members',
|
|
243
|
+
conversationId,
|
|
244
|
+
userId,
|
|
245
|
+
operation: value.operation,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
166
248
|
const text = readRequiredString(value.text);
|
|
167
249
|
const messageId = value.messageId === undefined
|
|
168
250
|
? undefined
|
|
@@ -207,16 +289,21 @@ export function parseCommunicateToolInput(value) {
|
|
|
207
289
|
throw new Error('communicate.action is invalid');
|
|
208
290
|
}
|
|
209
291
|
function renderResult(result) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
292
|
+
let id;
|
|
293
|
+
if (result.status === 'messaged'
|
|
294
|
+
|| result.status === 'forwarded'
|
|
295
|
+
|| result.status === 'shared') {
|
|
296
|
+
id = result.messageId;
|
|
297
|
+
}
|
|
298
|
+
else if (result.status === 'added' || result.status === 'removed') {
|
|
299
|
+
id = result.userId;
|
|
300
|
+
}
|
|
301
|
+
else if (result.status === 'created') {
|
|
302
|
+
id = result.conversationId;
|
|
303
|
+
}
|
|
304
|
+
else if (result.status === 'requested' || result.status === 'pending') {
|
|
305
|
+
id = result.requestId;
|
|
306
|
+
}
|
|
220
307
|
return {
|
|
221
308
|
content: [{
|
|
222
309
|
type: 'text',
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, type ExecuteVerbToolOptions, type VerbExecutionContext, type VerbProjectionOptions, type VerbToolDefinition, type VerbToolResult, } from './verb-tools.js';
|
|
1
|
+
export { CANON_TOOL_VERBS, CANON_COMMUNICATE_REPLACED_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, isCommunicateReplacedVerb, normalizeVerbToolArgs, type ExecuteVerbToolOptions, type VerbExecutionContext, type VerbProjectionOptions, type VerbToolDefinition, type VerbToolResult, } from './verb-tools.js';
|
|
2
2
|
export { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer, type CanonVerbMcpServerOptions, } from './verb-mcp.js';
|
|
3
3
|
export { CANON_COMMUNICATE_TOOL_NAME, canonCommunicateToolDefinition, createCanonCommunicationBinding, executeCanonCommunicateTool, parseCommunicateToolInput, type CanonCommunicationBinding, } from './communication-tool.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, } from './verb-tools.js';
|
|
1
|
+
export { CANON_TOOL_VERBS, CANON_COMMUNICATE_REPLACED_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, isCommunicateReplacedVerb, normalizeVerbToolArgs, } from './verb-tools.js';
|
|
2
2
|
export { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer, } from './verb-mcp.js';
|
|
3
3
|
export { CANON_COMMUNICATE_TOOL_NAME, canonCommunicateToolDefinition, createCanonCommunicationBinding, executeCanonCommunicateTool, parseCommunicateToolInput, } from './communication-tool.js';
|
package/dist/verb-mcp.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
22
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
-
import { canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, } from './verb-tools.js';
|
|
23
|
+
import { canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, isCommunicateReplacedVerb, } from './verb-tools.js';
|
|
24
24
|
import { createCanonCommunicationBinding } from './communication-tool.js';
|
|
25
25
|
/** MCP server name — verbs appear to the model as `mcp__canon__<verb>`. */
|
|
26
26
|
export const CANON_VERB_MCP_SERVER_NAME = 'canon';
|
|
@@ -47,6 +47,7 @@ onVerbCall, options = {}) {
|
|
|
47
47
|
const mcpServer = new McpServer({ name: CANON_VERB_MCP_SERVER_NAME, version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
48
48
|
const server = mcpServer.server;
|
|
49
49
|
const communication = createCanonCommunicationBinding();
|
|
50
|
+
const compactCommunication = options.communication !== undefined;
|
|
50
51
|
// The projection must tell the model what the dispatch below actually
|
|
51
52
|
// does: waiting posture (waitForResult: true), and conversation-scoped
|
|
52
53
|
// exactly when the binding supplies a context.
|
|
@@ -55,6 +56,7 @@ onVerbCall, options = {}) {
|
|
|
55
56
|
...canonVerbToolDefinitions({
|
|
56
57
|
interaction: 'waiting',
|
|
57
58
|
conversationScoped: Boolean(getContext),
|
|
59
|
+
compactCommunication,
|
|
58
60
|
}),
|
|
59
61
|
...(options.communication ? communication.tools : []),
|
|
60
62
|
],
|
|
@@ -75,7 +77,8 @@ onVerbCall, options = {}) {
|
|
|
75
77
|
...(result.isError ? { isError: true } : {}),
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
|
-
if (!isCanonToolVerb(name)
|
|
80
|
+
if (!isCanonToolVerb(name)
|
|
81
|
+
|| (compactCommunication && isCommunicateReplacedVerb(name))) {
|
|
79
82
|
return {
|
|
80
83
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
81
84
|
isError: true,
|
package/dist/verb-tools.d.ts
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { type CanonClient, type CanonVerbName } from '@canonmsg/core';
|
|
27
27
|
export declare const CANON_TOOL_VERBS: CanonVerbName[];
|
|
28
|
+
/** Canonical operations represented by the single model-facing communicate tool. */
|
|
29
|
+
export declare const CANON_COMMUNICATE_REPLACED_VERBS: readonly ["share_contact", "add_member", "remove_member", "leave_conversation", "list_contacts", "list_conversations"];
|
|
30
|
+
export declare function isCommunicateReplacedVerb(name: string): name is CanonVerbName;
|
|
28
31
|
export interface VerbToolDefinition {
|
|
29
32
|
name: string;
|
|
30
33
|
description: string;
|
|
@@ -43,6 +46,11 @@ export interface VerbProjectionOptions {
|
|
|
43
46
|
* (default) mark conversationId required from the model instead.
|
|
44
47
|
*/
|
|
45
48
|
conversationScoped?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Keep social operations behind the single `communicate` tool. Canonical
|
|
51
|
+
* verbs remain executable internally but are omitted from the model schema.
|
|
52
|
+
*/
|
|
53
|
+
compactCommunication?: boolean;
|
|
46
54
|
}
|
|
47
55
|
/** MCP tool definitions projected from the contract, shaped per binding. */
|
|
48
56
|
export declare function canonVerbToolDefinitions(options?: VerbProjectionOptions): VerbToolDefinition[];
|
package/dist/verb-tools.js
CHANGED
|
@@ -43,7 +43,6 @@ const CANON_VERB_TOOL_DESCRIPTIONS = {
|
|
|
43
43
|
remove_member: 'Remove a member from a Canon group (requires owner/admin role).',
|
|
44
44
|
leave_conversation: 'Leave a Canon group conversation.',
|
|
45
45
|
list_contacts: 'List your Canon contacts.',
|
|
46
|
-
list_contact_requests: 'List pending inbound contact requests (read-only awareness).',
|
|
47
46
|
list_conversations: 'List your Canon conversations (optionally limited).',
|
|
48
47
|
no_reply: 'End your turn without posting anything to the conversation. Use it in '
|
|
49
48
|
+ 'groups when you have nothing to add — no message is created, so no '
|
|
@@ -71,12 +70,28 @@ const INTERACTION_POSTURE_NOTES = {
|
|
|
71
70
|
},
|
|
72
71
|
};
|
|
73
72
|
export const CANON_TOOL_VERBS = Object.keys(CANON_VERB_TOOL_DESCRIPTIONS);
|
|
73
|
+
/** Canonical operations represented by the single model-facing communicate tool. */
|
|
74
|
+
export const CANON_COMMUNICATE_REPLACED_VERBS = [
|
|
75
|
+
'share_contact',
|
|
76
|
+
'add_member',
|
|
77
|
+
'remove_member',
|
|
78
|
+
'leave_conversation',
|
|
79
|
+
'list_contacts',
|
|
80
|
+
'list_conversations',
|
|
81
|
+
];
|
|
82
|
+
const COMMUNICATE_REPLACED_VERB_SET = new Set(CANON_COMMUNICATE_REPLACED_VERBS);
|
|
83
|
+
export function isCommunicateReplacedVerb(name) {
|
|
84
|
+
return COMMUNICATE_REPLACED_VERB_SET.has(name);
|
|
85
|
+
}
|
|
74
86
|
const CARD_VERBS = new Set(['send_card', 'request_card']);
|
|
75
87
|
const RUNTIME_OWNED_TOOL_FIELDS = ['native', 'runtimeId', 'turnId'];
|
|
76
88
|
/** MCP tool definitions projected from the contract, shaped per binding. */
|
|
77
89
|
export function canonVerbToolDefinitions(options = {}) {
|
|
78
90
|
const interaction = options.interaction ?? 'notify';
|
|
79
|
-
|
|
91
|
+
const projectedVerbs = options.compactCommunication
|
|
92
|
+
? CANON_TOOL_VERBS.filter((verb) => !COMMUNICATE_REPLACED_VERB_SET.has(verb))
|
|
93
|
+
: CANON_TOOL_VERBS;
|
|
94
|
+
return projectedVerbs.map((verb) => {
|
|
80
95
|
let inputSchema = getVerbInputSchema(verb, CARD_VERBS.has(verb) ? { cardSchema: RUNTIME_CARD_JSON_SCHEMA_V1 } : undefined);
|
|
81
96
|
if (isInteractionVerb(verb)) {
|
|
82
97
|
const properties = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Canonical Canon verb tools — shared projections of canon.verbs.v1 for runtime bindings",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../core ../rich-cards",
|
|
19
19
|
"build": "npm run prepare:workspace-deps && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
|
|
20
|
+
"sync:hermes-schema": "npm run build && node scripts/sync-hermes-communication-schema.mjs",
|
|
20
21
|
"dev": "tsc --watch",
|
|
21
22
|
"test": "vitest run",
|
|
22
23
|
"prepack": "npm run build"
|
|
@@ -40,8 +41,8 @@
|
|
|
40
41
|
"access": "public"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"@canonmsg/core": "^
|
|
44
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
44
|
+
"@canonmsg/core": "^12.0.0",
|
|
45
|
+
"@canonmsg/rich-cards": "^0.10.4",
|
|
45
46
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|