@canonmsg/agent-tools 0.5.3 → 0.7.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 CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  Model-facing tool definitions for the canonical Canon verbs, plus the dispatch that executes them.
4
4
 
5
- This is the public tool surface of Canon's verb layer. All seventeen `canon.verbs.v1` verbs — `send_to`, `request_input`, `request_approval`, `check_approval`, `send_card`, `request_card`, `share_contact`, `react`, `forward`, `create_group`, `add_member`, `remove_member`, `leave_conversation`, `list_contacts`, `list_contact_requests`, `list_conversations`, `no_reply` — are projected here as JSON-Schema tool definitions and dispatched over one endpoint: `POST /agent/verbs/:verb`.
5
+ This is the model-facing projection of Canon's verb layer. Everyday outbound
6
+ communication uses one `communicate` tool for existing messages, new direct
7
+ conversations, group creation, and forwarding. Interaction, card, reaction,
8
+ contact-sharing, group-management, read, and `no_reply` verbs remain separate
9
+ where their model interaction is materially different. Everything dispatches
10
+ through the canonical `POST /agent/verbs/:verb` endpoints.
6
11
 
7
12
  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.
8
13
 
@@ -0,0 +1,20 @@
1
+ import { type CanonClient, type CommunicateInput } from '@canonmsg/core';
2
+ import type { VerbToolDefinition, VerbToolResult } from './verb-tools.js';
3
+ export declare const CANON_COMMUNICATE_TOOL_NAME = "communicate";
4
+ /**
5
+ * The complete model-facing communication surface. It is exported separately
6
+ * from the in-conversation verb list so each runtime can opt into it.
7
+ */
8
+ export declare function canonCommunicateToolDefinition(name?: string): VerbToolDefinition;
9
+ /** Runtime validation mirrors the strict schema for bindings that dispatch directly. */
10
+ export declare function parseCommunicateToolInput(value: unknown): CommunicateInput;
11
+ export declare function executeCanonCommunicateTool(client: CanonClient, args: unknown): Promise<VerbToolResult>;
12
+ export interface CanonCommunicationBinding {
13
+ tools: ReadonlyArray<VerbToolDefinition>;
14
+ isToolName(name: string): boolean;
15
+ execute(client: CanonClient, toolName: string, args: unknown): Promise<VerbToolResult>;
16
+ }
17
+ /** A one-tool binding that runtimes may mount without adding any other tools. */
18
+ export declare function createCanonCommunicationBinding(options?: {
19
+ toolName?: string;
20
+ }): CanonCommunicationBinding;
@@ -0,0 +1,259 @@
1
+ import { parseDirectConversationSelection, VERB_LIMITS, } from '@canonmsg/core';
2
+ export const CANON_COMMUNICATE_TOOL_NAME = 'communicate';
3
+ const NON_EMPTY_ID = { type: 'string', minLength: 1, maxLength: 160 };
4
+ const MESSAGE_TEXT = { type: 'string', minLength: 1, maxLength: 4096 };
5
+ const OPTIONAL_CAPTION = { type: 'string', maxLength: 4096 };
6
+ const DIRECT_SELECTION_SCHEMA = {
7
+ oneOf: [
8
+ {
9
+ type: 'object',
10
+ required: ['mode'],
11
+ additionalProperties: false,
12
+ properties: { mode: { const: 'latest_or_new' } },
13
+ },
14
+ {
15
+ type: 'object',
16
+ required: ['mode'],
17
+ additionalProperties: false,
18
+ properties: { mode: { const: 'new' } },
19
+ },
20
+ {
21
+ type: 'object',
22
+ required: ['mode', 'conversationId'],
23
+ additionalProperties: false,
24
+ properties: {
25
+ mode: { const: 'specific' },
26
+ conversationId: NON_EMPTY_ID,
27
+ },
28
+ },
29
+ ],
30
+ };
31
+ /**
32
+ * The complete model-facing communication surface. It is exported separately
33
+ * from the in-conversation verb list so each runtime can opt into it.
34
+ */
35
+ export function canonCommunicateToolDefinition(name = CANON_COMMUNICATE_TOOL_NAME) {
36
+ return {
37
+ name,
38
+ description: 'Communicate in Canon. Message a known conversation, start/continue a '
39
+ + 'direct conversation, create a group, or forward an exact message. Canon '
40
+ + 'enforces the recipient and agent-deployer policies; a new direct '
41
+ + 'conversation or group member may require approval.',
42
+ inputSchema: {
43
+ type: 'object',
44
+ oneOf: [
45
+ {
46
+ type: 'object',
47
+ required: ['action', 'conversationId', 'text'],
48
+ additionalProperties: false,
49
+ properties: {
50
+ action: { const: 'message_existing' },
51
+ conversationId: NON_EMPTY_ID,
52
+ text: MESSAGE_TEXT,
53
+ messageId: NON_EMPTY_ID,
54
+ },
55
+ },
56
+ {
57
+ type: 'object',
58
+ required: ['action', 'principalId', 'text'],
59
+ additionalProperties: false,
60
+ properties: {
61
+ action: { const: 'start_direct' },
62
+ principalId: NON_EMPTY_ID,
63
+ text: MESSAGE_TEXT,
64
+ selection: DIRECT_SELECTION_SCHEMA,
65
+ messageId: NON_EMPTY_ID,
66
+ },
67
+ },
68
+ {
69
+ type: 'object',
70
+ required: ['action', 'name', 'memberIds'],
71
+ additionalProperties: false,
72
+ properties: {
73
+ action: { const: 'create_group' },
74
+ name: { type: 'string', minLength: 1, maxLength: VERB_LIMITS.groupNameChars },
75
+ memberIds: {
76
+ type: 'array',
77
+ minItems: 1,
78
+ maxItems: VERB_LIMITS.groupMembers - 1,
79
+ uniqueItems: true,
80
+ items: NON_EMPTY_ID,
81
+ },
82
+ },
83
+ },
84
+ {
85
+ type: 'object',
86
+ required: [
87
+ 'action',
88
+ 'sourceConversationId',
89
+ 'targetConversationId',
90
+ 'messageId',
91
+ ],
92
+ additionalProperties: false,
93
+ properties: {
94
+ action: { const: 'forward_message' },
95
+ sourceConversationId: NON_EMPTY_ID,
96
+ targetConversationId: NON_EMPTY_ID,
97
+ messageId: NON_EMPTY_ID,
98
+ text: OPTIONAL_CAPTION,
99
+ },
100
+ },
101
+ ],
102
+ },
103
+ };
104
+ }
105
+ function isRecord(value) {
106
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
107
+ }
108
+ function readRequiredString(value) {
109
+ return typeof value === 'string' && value.trim() ? value : null;
110
+ }
111
+ function hasOnlyKeys(record, allowed) {
112
+ return Object.keys(record).every((key) => allowed.has(key));
113
+ }
114
+ /** Runtime validation mirrors the strict schema for bindings that dispatch directly. */
115
+ export function parseCommunicateToolInput(value) {
116
+ if (!isRecord(value))
117
+ throw new Error('communicate arguments must be an object');
118
+ if (value.action === 'create_group') {
119
+ if (!hasOnlyKeys(value, new Set(['action', 'name', 'memberIds']))) {
120
+ throw new Error('create_group contains unsupported fields');
121
+ }
122
+ const name = readRequiredString(value.name);
123
+ if (!name)
124
+ throw new Error('communicate.name is required');
125
+ if (!Array.isArray(value.memberIds)
126
+ || value.memberIds.length === 0
127
+ || !value.memberIds.every((entry) => readRequiredString(entry) !== null)) {
128
+ throw new Error('communicate.memberIds must contain at least one principal id');
129
+ }
130
+ return {
131
+ action: 'create_group',
132
+ name,
133
+ memberIds: [...new Set(value.memberIds)],
134
+ };
135
+ }
136
+ if (value.action === 'forward_message') {
137
+ if (!hasOnlyKeys(value, new Set([
138
+ 'action',
139
+ 'sourceConversationId',
140
+ 'targetConversationId',
141
+ 'messageId',
142
+ 'text',
143
+ ]))) {
144
+ throw new Error('forward_message contains unsupported fields');
145
+ }
146
+ const sourceConversationId = readRequiredString(value.sourceConversationId);
147
+ const targetConversationId = readRequiredString(value.targetConversationId);
148
+ const messageId = readRequiredString(value.messageId);
149
+ if (!sourceConversationId)
150
+ throw new Error('communicate.sourceConversationId is required');
151
+ if (!targetConversationId)
152
+ throw new Error('communicate.targetConversationId is required');
153
+ if (!messageId)
154
+ throw new Error('communicate.messageId is required');
155
+ if (value.text !== undefined && typeof value.text !== 'string') {
156
+ throw new Error('communicate.text must be a string');
157
+ }
158
+ return {
159
+ action: 'forward_message',
160
+ sourceConversationId,
161
+ targetConversationId,
162
+ messageId,
163
+ ...(value.text !== undefined ? { text: value.text } : {}),
164
+ };
165
+ }
166
+ const text = readRequiredString(value.text);
167
+ const messageId = value.messageId === undefined
168
+ ? undefined
169
+ : readRequiredString(value.messageId);
170
+ if (!text)
171
+ throw new Error('communicate.text is required');
172
+ if (value.messageId !== undefined && !messageId) {
173
+ throw new Error('communicate.messageId must be a non-empty string');
174
+ }
175
+ if (value.action === 'message_existing') {
176
+ if (!hasOnlyKeys(value, new Set(['action', 'conversationId', 'text', 'messageId']))) {
177
+ throw new Error('message_existing contains unsupported fields');
178
+ }
179
+ const conversationId = readRequiredString(value.conversationId);
180
+ if (!conversationId)
181
+ throw new Error('communicate.conversationId is required');
182
+ return {
183
+ action: 'message_existing',
184
+ conversationId,
185
+ text,
186
+ ...(messageId ? { messageId } : {}),
187
+ };
188
+ }
189
+ if (value.action === 'start_direct') {
190
+ if (!hasOnlyKeys(value, new Set(['action', 'principalId', 'text', 'selection', 'messageId']))) {
191
+ throw new Error('start_direct contains unsupported fields');
192
+ }
193
+ const principalId = readRequiredString(value.principalId);
194
+ if (!principalId)
195
+ throw new Error('communicate.principalId is required');
196
+ const selection = value.selection === undefined
197
+ ? undefined
198
+ : parseDirectConversationSelection(value.selection);
199
+ return {
200
+ action: 'start_direct',
201
+ principalId,
202
+ text,
203
+ ...(selection ? { selection } : {}),
204
+ ...(messageId ? { messageId } : {}),
205
+ };
206
+ }
207
+ throw new Error('communicate.action is invalid');
208
+ }
209
+ function renderResult(result) {
210
+ const id = result.status === 'messaged'
211
+ ? result.messageId
212
+ : result.status === 'forwarded'
213
+ ? result.messageId
214
+ : result.status === 'created'
215
+ ? result.conversationId
216
+ : result.status === 'requested'
217
+ || result.status === 'pending'
218
+ ? result.requestId
219
+ : undefined;
220
+ return {
221
+ content: [{
222
+ type: 'text',
223
+ text: `communicate: ${result.status}${id ? ` (${id})` : ''}\n${JSON.stringify(result, null, 2)}`,
224
+ }],
225
+ };
226
+ }
227
+ export async function executeCanonCommunicateTool(client, args) {
228
+ let input;
229
+ try {
230
+ input = parseCommunicateToolInput(args);
231
+ }
232
+ catch (error) {
233
+ return {
234
+ content: [{
235
+ type: 'text',
236
+ text: `Invalid communicate arguments: ${error.message}`,
237
+ }],
238
+ isError: true,
239
+ };
240
+ }
241
+ return renderResult(await client.communicate(input));
242
+ }
243
+ /** A one-tool binding that runtimes may mount without adding any other tools. */
244
+ export function createCanonCommunicationBinding(options = {}) {
245
+ const toolName = options.toolName?.trim() || CANON_COMMUNICATE_TOOL_NAME;
246
+ return {
247
+ tools: [canonCommunicateToolDefinition(toolName)],
248
+ isToolName: (name) => name === toolName,
249
+ execute: (client, name, args) => {
250
+ if (name !== toolName) {
251
+ return Promise.resolve({
252
+ content: [{ type: 'text', text: `Unsupported Canon communication tool: ${name}` }],
253
+ isError: true,
254
+ });
255
+ }
256
+ return executeCanonCommunicateTool(client, args);
257
+ },
258
+ };
259
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, stampSendToTurnComplete, type ExecuteVerbToolOptions, type VerbExecutionContext, type VerbProjectionOptions, type VerbToolDefinition, type VerbToolResult, } from './verb-tools.js';
2
- export { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer, } from './verb-mcp.js';
1
+ export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, type ExecuteVerbToolOptions, type VerbExecutionContext, type VerbProjectionOptions, type VerbToolDefinition, type VerbToolResult, } from './verb-tools.js';
2
+ export { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer, type CanonVerbMcpServerOptions, } from './verb-mcp.js';
3
+ export { CANON_COMMUNICATE_TOOL_NAME, canonCommunicateToolDefinition, createCanonCommunicationBinding, executeCanonCommunicateTool, parseCommunicateToolInput, type CanonCommunicationBinding, } from './communication-tool.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, stampSendToTurnComplete, } from './verb-tools.js';
1
+ export { CANON_TOOL_VERBS, canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, normalizeVerbToolArgs, } from './verb-tools.js';
2
2
  export { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer, } from './verb-mcp.js';
3
+ export { CANON_COMMUNICATE_TOOL_NAME, canonCommunicateToolDefinition, createCanonCommunicationBinding, executeCanonCommunicateTool, parseCommunicateToolInput, } from './communication-tool.js';
@@ -23,6 +23,13 @@ import type { CanonClient, CanonVerbName } from '@canonmsg/core';
23
23
  import { type VerbExecutionContext } from './verb-tools.js';
24
24
  /** MCP server name — verbs appear to the model as `mcp__canon__<verb>`. */
25
25
  export declare const CANON_VERB_MCP_SERVER_NAME = "canon";
26
+ export interface CanonVerbMcpServerOptions {
27
+ /**
28
+ * Add the compact principal-neutral `communicate` tool. The broad `send_to`
29
+ * verb is never model-visible; all conversation-scoped verbs remain unchanged.
30
+ */
31
+ communication?: boolean;
32
+ }
26
33
  export declare function createCanonVerbMcpServer(getClient: () => CanonClient | null,
27
34
  /**
28
35
  * Lazy per-call execution context (host sessions are conversation-scoped:
@@ -37,4 +44,4 @@ getContext?: () => VerbExecutionContext | undefined,
37
44
  * un-deployed server, so `no_reply` degrades to "silent turn, error shown to
38
45
  * the model" rather than "silence never happens".
39
46
  */
40
- onVerbCall?: (verb: CanonVerbName, args: Record<string, unknown>, context: VerbExecutionContext | undefined) => void): McpServer;
47
+ onVerbCall?: (verb: CanonVerbName, args: Record<string, unknown>, context: VerbExecutionContext | undefined) => void, options?: CanonVerbMcpServerOptions): McpServer;
package/dist/verb-mcp.js CHANGED
@@ -20,7 +20,8 @@
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, stampSendToTurnComplete, } from './verb-tools.js';
23
+ import { canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, } from './verb-tools.js';
24
+ import { createCanonCommunicationBinding } from './communication-tool.js';
24
25
  /** MCP server name — verbs appear to the model as `mcp__canon__<verb>`. */
25
26
  export const CANON_VERB_MCP_SERVER_NAME = 'canon';
26
27
  export function createCanonVerbMcpServer(getClient,
@@ -37,7 +38,7 @@ getContext,
37
38
  * un-deployed server, so `no_reply` degrades to "silent turn, error shown to
38
39
  * the model" rather than "silence never happens".
39
40
  */
40
- onVerbCall) {
41
+ onVerbCall, options = {}) {
41
42
  // The Agent SDK's mcpServers option expects the high-level McpServer class,
42
43
  // but its registerTool API wants Zod shapes — so the contract's JSON-Schema
43
44
  // projections are installed directly on the underlying protocol server
@@ -45,14 +46,18 @@ onVerbCall) {
45
46
  // tool handlers).
46
47
  const mcpServer = new McpServer({ name: CANON_VERB_MCP_SERVER_NAME, version: '1.0.0' }, { capabilities: { tools: {} } });
47
48
  const server = mcpServer.server;
49
+ const communication = createCanonCommunicationBinding();
48
50
  // The projection must tell the model what the dispatch below actually
49
51
  // does: waiting posture (waitForResult: true), and conversation-scoped
50
52
  // exactly when the binding supplies a context.
51
53
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
52
- tools: canonVerbToolDefinitions({
53
- interaction: 'waiting',
54
- conversationScoped: Boolean(getContext),
55
- }),
54
+ tools: [
55
+ ...canonVerbToolDefinitions({
56
+ interaction: 'waiting',
57
+ conversationScoped: Boolean(getContext),
58
+ }),
59
+ ...(options.communication ? communication.tools : []),
60
+ ],
56
61
  }));
57
62
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
58
63
  const client = getClient();
@@ -63,6 +68,13 @@ onVerbCall) {
63
68
  };
64
69
  }
65
70
  const name = request.params.name;
71
+ if (options.communication && communication.isToolName(name)) {
72
+ const result = await communication.execute(client, name, request.params.arguments);
73
+ return {
74
+ content: result.content.map((item) => ({ type: 'text', text: item.text })),
75
+ ...(result.isError ? { isError: true } : {}),
76
+ };
77
+ }
66
78
  if (!isCanonToolVerb(name)) {
67
79
  return {
68
80
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
@@ -72,7 +84,7 @@ onVerbCall) {
72
84
  const rawArgs = request.params.arguments && typeof request.params.arguments === 'object'
73
85
  ? { ...request.params.arguments }
74
86
  : {};
75
- const verbArgs = name === 'send_to' ? stampSendToTurnComplete(rawArgs) : rawArgs;
87
+ const verbArgs = rawArgs;
76
88
  const context = getContext?.();
77
89
  try {
78
90
  onVerbCall?.(name, verbArgs, context);
@@ -47,13 +47,6 @@ export interface VerbProjectionOptions {
47
47
  /** MCP tool definitions projected from the contract, shaped per binding. */
48
48
  export declare function canonVerbToolDefinitions(options?: VerbProjectionOptions): VerbToolDefinition[];
49
49
  export declare function isCanonToolVerb(name: string): name is CanonVerbName;
50
- /**
51
- * Outbound sends from a Claude surface are completed turns: stamp
52
- * turnSemantics 'turn_complete' into send_to messageOptions.metadata unless
53
- * the caller already set turn semantics. Shared by the channel server and
54
- * the host-mode MCP mount.
55
- */
56
- export declare function stampSendToTurnComplete(args: Record<string, unknown>): Record<string, unknown>;
57
50
  export interface VerbToolResult {
58
51
  content: Array<{
59
52
  type: 'text';
@@ -28,10 +28,6 @@ import { findVerbByteLimitViolations, getVerbInputSchema, projectVerbIntentToWir
28
28
  import { RUNTIME_CARD_JSON_SCHEMA_V1 } from '@canonmsg/rich-cards';
29
29
  /** Verbs exposed to models, with binding-level descriptions. */
30
30
  const CANON_VERB_TOOL_DESCRIPTIONS = {
31
- send_to: 'Send a Canon message to another conversation or user. Supports a private '
32
- + 'selfContext note (with sourceConversationId) so you recognize your own '
33
- + 'cross-conversation transfer later. Admission-aware for user targets: '
34
- + 'may return requested/pending (contact request) instead of messaged.',
35
31
  request_input: 'Ask a human in a Canon conversation a structured question (input card).',
36
32
  request_approval: 'Ask a human to allow/deny an action via a Canon approval card. Use '
37
33
  + "mode 'detached' and poll with check_approval for long-lived approvals.",
@@ -43,10 +39,6 @@ const CANON_VERB_TOOL_DESCRIPTIONS = {
43
39
  share_contact: "Share a contact card into a conversation. The shared user must be in "
44
40
  + 'your contacts.',
45
41
  react: 'Toggle an emoji reaction on a Canon message.',
46
- forward: 'Forward an existing Canon message into another conversation, with an optional caption.',
47
- create_group: 'Create a Canon group conversation. Directly-addable members join at '
48
- + 'creation; policy approval and agent-owner setup become pending invites '
49
- + '(result.pending); hard-denied members are skipped (result.skipped).',
50
42
  add_member: 'Add a member to a Canon group (policy approval and/or agent-owner setup may return a pending invite).',
51
43
  remove_member: 'Remove a member from a Canon group (requires owner/admin role).',
52
44
  leave_conversation: 'Leave a Canon group conversation.',
@@ -124,20 +116,6 @@ export function canonVerbToolDefinitions(options = {}) {
124
116
  export function isCanonToolVerb(name) {
125
117
  return CANON_TOOL_VERBS.includes(name);
126
118
  }
127
- /**
128
- * Outbound sends from a Claude surface are completed turns: stamp
129
- * turnSemantics 'turn_complete' into send_to messageOptions.metadata unless
130
- * the caller already set turn semantics. Shared by the channel server and
131
- * the host-mode MCP mount.
132
- */
133
- export function stampSendToTurnComplete(args) {
134
- const options = { ...(args.messageOptions ?? {}) };
135
- options.metadata = {
136
- turnSemantics: 'turn_complete',
137
- ...(options.metadata ?? {}),
138
- };
139
- return { ...args, messageOptions: options };
140
- }
141
119
  /** Verbs whose intents carry context-bound fields the binding must complete. */
142
120
  const INTERACTION_VERBS = new Set([
143
121
  'request_input',
@@ -376,23 +354,6 @@ export async function executeCanonVerbTool(client, verb, args, options = {}) {
376
354
  return { content: [{ type: 'text', text: `Invalid arguments: ${normalized.error}` }], isError: true };
377
355
  }
378
356
  args = normalized.args;
379
- // canonContactId is a contact-card identity, not a wire field: resolve it
380
- // to a targetUserId here (the two-step the contract documents) so the
381
- // advertised send_to schema is honest in every binding.
382
- if (verb === 'send_to' && typeof args.canonContactId === 'string') {
383
- const resolved = await client.resolveAdmission({ canonContactId: args.canonContactId });
384
- if (!resolved.resolvedTargetUserId) {
385
- return {
386
- content: [{
387
- type: 'text',
388
- text: `send_to: unavailable\n${JSON.stringify({ status: 'unavailable', reason: 'canonContactId could not be resolved' }, null, 2)}`,
389
- }],
390
- isError: true,
391
- };
392
- }
393
- const { canonContactId: _resolved, ...rest } = args;
394
- args = { ...rest, targetUserId: resolved.resolvedTargetUserId };
395
- }
396
357
  const violations = findVerbByteLimitViolations(verb, args);
397
358
  if (violations.length > 0) {
398
359
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-tools",
3
- "version": "0.5.3",
3
+ "version": "0.7.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",
@@ -40,8 +40,8 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@canonmsg/core": "^10.4.0",
44
- "@canonmsg/rich-cards": "^0.10.2",
43
+ "@canonmsg/core": "^11.0.0",
44
+ "@canonmsg/rich-cards": "^0.10.3",
45
45
  "@modelcontextprotocol/sdk": "^1.30.0"
46
46
  },
47
47
  "devDependencies": {