Skip to content

Commit 978557b

Browse files
authored
[AI-6713] Migrate to registerTool() API and add _meta types (#26)
1 parent ba8417a commit 978557b

File tree

4 files changed

+195
-77
lines changed

4 files changed

+195
-77
lines changed

docs/tool-annotations.md

Lines changed: 104 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,50 @@
11
# Angie Tool Annotations
22

3-
Angie exposes a set of annotation constants and interfaces that MCP tool authors can use to attach Angie-specific metadata to their tools. These are exported directly from `@elementor/angie-sdk`.
3+
Angie exposes a set of annotation constants and interfaces that MCP tool authors can use to attach metadata to their tools. These are exported directly from `@elementor/angie-sdk`.
44

5-
## Available Annotations
5+
## Annotations vs `_meta`
6+
7+
The MCP protocol distinguishes between two types of tool metadata:
8+
9+
| Location | Purpose | Interface |
10+
|---|---|---|
11+
| `annotations` | **Standard MCP annotations** — recognized by the MCP protocol | `AngieToolAnnotations` |
12+
| `_meta` | **Custom Angie metadata** — vendor-specific extensions | `AngieToolMeta` |
13+
14+
### Standard Annotations (`annotations`)
15+
16+
| Field | Type | Purpose |
17+
|---|---|---|
18+
| `title` | `string` | Human-readable title for the tool |
19+
| `readOnlyHint` | `boolean` | Mark a tool as read-only |
20+
| `destructiveHint` | `boolean` | Mark a tool as potentially destructive |
21+
22+
### Custom Angie Metadata (`_meta`)
623

724
| Constant | Value | Purpose |
825
|---|---|---|
926
| `ANGIE_REQUIRED_RESOURCES` | `'angie/requiredResources'` | Declare resources the tool needs |
1027
| `ANGIE_MODEL_PREFERENCES` | `'angie/modelPreferences'` | Request a specific AI model |
1128
| `ANGIE_EXTENDED_TIMEOUT` | `'angie/extendedTimeout'` | Request a longer execution timeout |
12-
| `MCP_READONLY` | `'readOnlyHint'` | Mark a tool as read-only |
29+
30+
---
31+
32+
## Tool Registration API
33+
34+
Use `server.registerTool()` to register tools with the MCP server:
35+
36+
```typescript
37+
server.registerTool(
38+
'tool-name',
39+
{
40+
description: 'Tool description',
41+
inputSchema: { /* zod schema */ },
42+
annotations: { /* standard MCP annotations */ },
43+
_meta: { /* custom Angie metadata */ },
44+
},
45+
async (args) => { /* handler */ }
46+
);
47+
```
1348

1449
---
1550

@@ -28,20 +63,22 @@ interface AngieRequiredResource {
2863
**Example:**
2964

3065
```typescript
31-
import { ANGIE_REQUIRED_RESOURCES, ToolAnnotations } from '@elementor/angie-sdk';
66+
import { ANGIE_REQUIRED_RESOURCES, AngieToolMeta } from '@elementor/angie-sdk';
3267

33-
server.tool(
68+
server.registerTool(
3469
'update-page-styles',
35-
'Updates the CSS styles for the current page',
36-
{ /* input schema */ },
3770
{
38-
[ANGIE_REQUIRED_RESOURCES]: [
39-
{
40-
uri: 'elementor://page/styles',
41-
whenToUse: 'Always — needed to read current page styles before updating',
42-
}
43-
]
44-
} as ToolAnnotations,
71+
description: 'Updates the CSS styles for the current page',
72+
inputSchema: { /* ... */ },
73+
_meta: {
74+
[ANGIE_REQUIRED_RESOURCES]: [
75+
{
76+
uri: 'elementor://page/styles',
77+
whenToUse: 'Always — needed to read current page styles before updating',
78+
}
79+
]
80+
} as AngieToolMeta,
81+
},
4582
async (args) => { /* handler */ }
4683
);
4784
```
@@ -72,21 +109,23 @@ Angie resolves the model in this order:
72109
**Example:**
73110

74111
```typescript
75-
import { ANGIE_MODEL_PREFERENCES, ToolAnnotations } from '@elementor/angie-sdk';
112+
import { ANGIE_MODEL_PREFERENCES, AngieToolMeta } from '@elementor/angie-sdk';
76113

77-
server.tool(
114+
server.registerTool(
78115
'generate-custom-css',
79-
'Generates CSS code based on design requirements',
80-
{ /* input schema */ },
81116
{
82-
[ANGIE_MODEL_PREFERENCES]: {
83-
hints: [
84-
{ name: 'claude-sonnet' }, // First choice
85-
{ name: 'gpt-4.1' } // Fallback
86-
],
87-
intelligencePriority: 0.9 // Optional: for future use
88-
}
89-
} as ToolAnnotations,
117+
description: 'Generates CSS code based on design requirements',
118+
inputSchema: { /* ... */ },
119+
_meta: {
120+
[ANGIE_MODEL_PREFERENCES]: {
121+
hints: [
122+
{ name: 'claude-sonnet' }, // First choice
123+
{ name: 'gpt-4.1' } // Fallback
124+
],
125+
intelligencePriority: 0.9 // Optional: for future use
126+
}
127+
} as AngieToolMeta,
128+
},
90129
async (args) => { /* handler */ }
91130
);
92131
```
@@ -108,39 +147,41 @@ interface AngieExtendedTimeout {
108147
**Example:**
109148

110149
```typescript
111-
import { ANGIE_EXTENDED_TIMEOUT, ToolAnnotations } from '@elementor/angie-sdk';
150+
import { ANGIE_EXTENDED_TIMEOUT, AngieToolMeta } from '@elementor/angie-sdk';
112151

113-
server.tool(
152+
server.registerTool(
114153
'bulk-update-elements',
115-
'Updates all elements on the page in one operation',
116-
{ /* input schema */ },
117154
{
118-
[ANGIE_EXTENDED_TIMEOUT]: {
119-
timeoutMs: 60000 // 60 seconds
120-
}
121-
} as ToolAnnotations,
155+
description: 'Updates all elements on the page in one operation',
156+
inputSchema: { /* ... */ },
157+
_meta: {
158+
[ANGIE_EXTENDED_TIMEOUT]: {
159+
timeoutMs: 60000 // 60 seconds
160+
}
161+
} as AngieToolMeta,
162+
},
122163
async (args) => { /* handler */ }
123164
);
124165
```
125166

126167
---
127168

128-
## `MCP_READONLY`
169+
## `readOnlyHint` (Standard MCP Annotation)
129170

130171
Mark a tool as read-only. Angie uses this hint to understand that the tool does not mutate any state, which can affect planning and user confirmation flows.
131172

132173
**Example:**
133174

134175
```typescript
135-
import { MCP_READONLY, ToolAnnotations } from '@elementor/angie-sdk';
136-
137-
server.tool(
176+
server.registerTool(
138177
'get-page-structure',
139-
'Returns the structure of the current page',
140-
{ /* input schema */ },
141178
{
142-
[MCP_READONLY]: true
143-
} as ToolAnnotations,
179+
description: 'Returns the structure of the current page',
180+
inputSchema: { /* ... */ },
181+
annotations: {
182+
readOnlyHint: true
183+
},
184+
},
144185
async (args) => { /* handler */ }
145186
);
146187
```
@@ -149,35 +190,38 @@ server.tool(
149190

150191
## Using Multiple Annotations Together
151192

152-
All annotations can be combined on a single tool:
193+
Standard annotations and custom Angie metadata can be combined on a single tool:
153194

154195
```typescript
155196
import {
156197
ANGIE_REQUIRED_RESOURCES,
157198
ANGIE_MODEL_PREFERENCES,
158199
ANGIE_EXTENDED_TIMEOUT,
159-
MCP_READONLY,
160-
ToolAnnotations,
200+
AngieToolMeta,
161201
} from '@elementor/angie-sdk';
162202

163-
server.tool(
203+
server.registerTool(
164204
'analyze-page-layout',
165-
'Analyzes the current page layout and returns suggestions',
166-
{ /* input schema */ },
167205
{
168-
[MCP_READONLY]: true,
169-
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 30000 },
170-
[ANGIE_REQUIRED_RESOURCES]: [
171-
{
172-
uri: 'elementor://page/layout',
173-
whenToUse: 'Always — needed to read the page structure',
206+
description: 'Analyzes the current page layout and returns suggestions',
207+
inputSchema: { /* ... */ },
208+
annotations: {
209+
readOnlyHint: true,
210+
},
211+
_meta: {
212+
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 30000 },
213+
[ANGIE_REQUIRED_RESOURCES]: [
214+
{
215+
uri: 'elementor://page/layout',
216+
whenToUse: 'Always — needed to read the page structure',
217+
}
218+
],
219+
[ANGIE_MODEL_PREFERENCES]: {
220+
hints: [{ name: 'claude-sonnet' }],
221+
intelligencePriority: 0.9
174222
}
175-
],
176-
[ANGIE_MODEL_PREFERENCES]: {
177-
hints: [{ name: 'claude-sonnet' }],
178-
intelligencePriority: 0.9
179-
}
180-
} as ToolAnnotations,
223+
} as AngieToolMeta,
224+
},
181225
async (args) => { /* handler */ }
182226
);
183227
```

example/angie-demo-plugin/src/demo-mcp-server.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,16 @@ function createSeoMcpServer() {
5858
}
5959
);
6060

61-
server.tool(
61+
server.registerTool(
6262
'analyze-page-seo',
63-
'Analyzes the SEO of the current page including meta tags, headings, and content structure',
6463
{
65-
url: z.string().describe( 'The URL of the page to analyze' ),
64+
description: 'Analyzes the SEO of the current page including meta tags, headings, and content structure',
65+
inputSchema: {
66+
url: z.string().describe( 'The URL of the page to analyze' ),
67+
},
68+
annotations: {
69+
readOnlyHint: true,
70+
},
6671
},
6772
async ( { url }: { url: string } ) => {
6873
const response = await makeApiRequest( 'angie-demo/v1/analyze-page-seo', { url } );
@@ -72,14 +77,17 @@ function createSeoMcpServer() {
7277
text: JSON.stringify( response, null, 2 ),
7378
} ],
7479
};
75-
} );
80+
}
81+
);
7682

77-
server.tool(
83+
server.registerTool(
7884
'manage-post-types',
79-
'Manages post types with Angie',
8085
{
81-
postType: z.string().describe( 'The post type to register' ),
82-
action: z.enum( [ 'register', 'unregister' ] ).describe( 'The action to perform' ),
86+
description: 'Manages post types with Angie',
87+
inputSchema: {
88+
postType: z.string().describe( 'The post type to register' ),
89+
action: z.enum( [ 'register', 'unregister' ] ).describe( 'The action to perform' ),
90+
},
8391
},
8492
async ( { postType, action }: { postType: string, action: string } ) => {
8593
const response = await makeApiRequest( 'angie-demo/v1/post-types', { postType, action } );
@@ -89,12 +97,18 @@ function createSeoMcpServer() {
8997
text: JSON.stringify( response, null, 2 ),
9098
} ],
9199
};
92-
} );
100+
}
101+
);
93102

94-
server.tool(
103+
server.registerTool(
95104
'security-check',
96-
'Checks the security of current WordPress installation',
97-
{},
105+
{
106+
description: 'Checks the security of current WordPress installation',
107+
inputSchema: {},
108+
annotations: {
109+
readOnlyHint: true,
110+
},
111+
},
98112
async () => {
99113
const response = await makeApiRequest( 'angie-demo/v1/security-check', {} );
100114
return {
@@ -103,11 +117,15 @@ function createSeoMcpServer() {
103117
text: JSON.stringify( response, null, 2 ),
104118
} ],
105119
};
106-
} );
120+
}
121+
);
107122

108-
server.tool( 'run-fireworks',
109-
'Creates a celebratory fireworks display effect on the current screen. Use this when you want to add visual excitement or celebrate a successful action. The tool will create a full-screen canvas overlay with animated fireworks that automatically stop after 5 seconds.',
110-
{},
123+
server.registerTool(
124+
'run-fireworks',
125+
{
126+
description: 'Creates a celebratory fireworks display effect on the current screen. Use this when you want to add visual excitement or celebrate a successful action. The tool will create a full-screen canvas overlay with animated fireworks that automatically stop after 5 seconds.',
127+
inputSchema: {},
128+
},
111129
async () => {
112130
try {
113131
// Create canvas element if it doesn't exist
@@ -161,7 +179,8 @@ function createSeoMcpServer() {
161179
} ],
162180
};
163181
}
164-
} );
182+
}
183+
);
165184

166185
return server;
167186
}

src/angie-annotations.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
ANGIE_MODEL_PREFERENCES,
55
ANGIE_EXTENDED_TIMEOUT,
66
MCP_READONLY,
7+
AngieToolMeta,
8+
AngieToolAnnotations,
79
} from './angie-annotations';
810

911
describe('angie-annotations', () => {
@@ -22,4 +24,26 @@ describe('angie-annotations', () => {
2224
it('MCP_READONLY has the correct value', () => {
2325
expect(MCP_READONLY).toBe('readOnlyHint');
2426
});
27+
28+
it('AngieToolMeta interface accepts valid custom metadata', () => {
29+
const meta: AngieToolMeta = {
30+
[ANGIE_REQUIRED_RESOURCES]: [{ uri: 'resource://test', whenToUse: 'always' }],
31+
[ANGIE_MODEL_PREFERENCES]: { intelligencePriority: 0.8 },
32+
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 60000 },
33+
};
34+
expect(meta[ANGIE_REQUIRED_RESOURCES]).toHaveLength(1);
35+
expect(meta[ANGIE_MODEL_PREFERENCES]?.intelligencePriority).toBe(0.8);
36+
expect(meta[ANGIE_EXTENDED_TIMEOUT]?.timeoutMs).toBe(60000);
37+
});
38+
39+
it('AngieToolAnnotations interface accepts standard MCP annotations', () => {
40+
const annotations: AngieToolAnnotations = {
41+
title: 'My Tool',
42+
readOnlyHint: true,
43+
destructiveHint: false,
44+
};
45+
expect(annotations.title).toBe('My Tool');
46+
expect(annotations.readOnlyHint).toBe(true);
47+
expect(annotations.destructiveHint).toBe(false);
48+
});
2549
});

0 commit comments

Comments
 (0)