Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 104 additions & 60 deletions docs/tool-annotations.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
# Angie Tool Annotations

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`.
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`.

## Available Annotations
## Annotations vs `_meta`

The MCP protocol distinguishes between two types of tool metadata:

| Location | Purpose | Interface |
|---|---|---|
| `annotations` | **Standard MCP annotations** — recognized by the MCP protocol | `AngieToolAnnotations` |
| `_meta` | **Custom Angie metadata** — vendor-specific extensions | `AngieToolMeta` |

### Standard Annotations (`annotations`)

| Field | Type | Purpose |
|---|---|---|
| `title` | `string` | Human-readable title for the tool |
| `readOnlyHint` | `boolean` | Mark a tool as read-only |
| `destructiveHint` | `boolean` | Mark a tool as potentially destructive |

### Custom Angie Metadata (`_meta`)

| Constant | Value | Purpose |
|---|---|---|
| `ANGIE_REQUIRED_RESOURCES` | `'angie/requiredResources'` | Declare resources the tool needs |
| `ANGIE_MODEL_PREFERENCES` | `'angie/modelPreferences'` | Request a specific AI model |
| `ANGIE_EXTENDED_TIMEOUT` | `'angie/extendedTimeout'` | Request a longer execution timeout |
| `MCP_READONLY` | `'readOnlyHint'` | Mark a tool as read-only |

---

## Tool Registration API

Use `server.registerTool()` to register tools with the MCP server:

```typescript
server.registerTool(
'tool-name',
{
description: 'Tool description',
inputSchema: { /* zod schema */ },
annotations: { /* standard MCP annotations */ },
_meta: { /* custom Angie metadata */ },
},
async (args) => { /* handler */ }
);
```

---

Expand All @@ -28,20 +63,22 @@ interface AngieRequiredResource {
**Example:**

```typescript
import { ANGIE_REQUIRED_RESOURCES, ToolAnnotations } from '@elementor/angie-sdk';
import { ANGIE_REQUIRED_RESOURCES, AngieToolMeta } from '@elementor/angie-sdk';

server.tool(
server.registerTool(
'update-page-styles',
'Updates the CSS styles for the current page',
{ /* input schema */ },
{
[ANGIE_REQUIRED_RESOURCES]: [
{
uri: 'elementor://page/styles',
whenToUse: 'Always — needed to read current page styles before updating',
}
]
} as ToolAnnotations,
description: 'Updates the CSS styles for the current page',
inputSchema: { /* ... */ },
_meta: {
[ANGIE_REQUIRED_RESOURCES]: [
{
uri: 'elementor://page/styles',
whenToUse: 'Always — needed to read current page styles before updating',
}
]
} as AngieToolMeta,
},
async (args) => { /* handler */ }
);
```
Expand Down Expand Up @@ -72,21 +109,23 @@ Angie resolves the model in this order:
**Example:**

```typescript
import { ANGIE_MODEL_PREFERENCES, ToolAnnotations } from '@elementor/angie-sdk';
import { ANGIE_MODEL_PREFERENCES, AngieToolMeta } from '@elementor/angie-sdk';

server.tool(
server.registerTool(
'generate-custom-css',
'Generates CSS code based on design requirements',
{ /* input schema */ },
{
[ANGIE_MODEL_PREFERENCES]: {
hints: [
{ name: 'claude-sonnet' }, // First choice
{ name: 'gpt-4.1' } // Fallback
],
intelligencePriority: 0.9 // Optional: for future use
}
} as ToolAnnotations,
description: 'Generates CSS code based on design requirements',
inputSchema: { /* ... */ },
_meta: {
[ANGIE_MODEL_PREFERENCES]: {
hints: [
{ name: 'claude-sonnet' }, // First choice
{ name: 'gpt-4.1' } // Fallback
],
intelligencePriority: 0.9 // Optional: for future use
}
} as AngieToolMeta,
},
async (args) => { /* handler */ }
);
```
Expand All @@ -108,39 +147,41 @@ interface AngieExtendedTimeout {
**Example:**

```typescript
import { ANGIE_EXTENDED_TIMEOUT, ToolAnnotations } from '@elementor/angie-sdk';
import { ANGIE_EXTENDED_TIMEOUT, AngieToolMeta } from '@elementor/angie-sdk';

server.tool(
server.registerTool(
'bulk-update-elements',
'Updates all elements on the page in one operation',
{ /* input schema */ },
{
[ANGIE_EXTENDED_TIMEOUT]: {
timeoutMs: 60000 // 60 seconds
}
} as ToolAnnotations,
description: 'Updates all elements on the page in one operation',
inputSchema: { /* ... */ },
_meta: {
[ANGIE_EXTENDED_TIMEOUT]: {
timeoutMs: 60000 // 60 seconds
}
} as AngieToolMeta,
},
async (args) => { /* handler */ }
);
```

---

## `MCP_READONLY`
## `readOnlyHint` (Standard MCP Annotation)

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.

**Example:**

```typescript
import { MCP_READONLY, ToolAnnotations } from '@elementor/angie-sdk';

server.tool(
server.registerTool(
'get-page-structure',
'Returns the structure of the current page',
{ /* input schema */ },
{
[MCP_READONLY]: true
} as ToolAnnotations,
description: 'Returns the structure of the current page',
inputSchema: { /* ... */ },
annotations: {
readOnlyHint: true
},
},
async (args) => { /* handler */ }
);
```
Expand All @@ -149,35 +190,38 @@ server.tool(

## Using Multiple Annotations Together

All annotations can be combined on a single tool:
Standard annotations and custom Angie metadata can be combined on a single tool:

```typescript
import {
ANGIE_REQUIRED_RESOURCES,
ANGIE_MODEL_PREFERENCES,
ANGIE_EXTENDED_TIMEOUT,
MCP_READONLY,
ToolAnnotations,
AngieToolMeta,
} from '@elementor/angie-sdk';

server.tool(
server.registerTool(
'analyze-page-layout',
'Analyzes the current page layout and returns suggestions',
{ /* input schema */ },
{
[MCP_READONLY]: true,
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 30000 },
[ANGIE_REQUIRED_RESOURCES]: [
{
uri: 'elementor://page/layout',
whenToUse: 'Always — needed to read the page structure',
description: 'Analyzes the current page layout and returns suggestions',
inputSchema: { /* ... */ },
annotations: {
readOnlyHint: true,
},
_meta: {
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 30000 },
[ANGIE_REQUIRED_RESOURCES]: [
{
uri: 'elementor://page/layout',
whenToUse: 'Always — needed to read the page structure',
}
],
[ANGIE_MODEL_PREFERENCES]: {
hints: [{ name: 'claude-sonnet' }],
intelligencePriority: 0.9
}
],
[ANGIE_MODEL_PREFERENCES]: {
hints: [{ name: 'claude-sonnet' }],
intelligencePriority: 0.9
}
} as ToolAnnotations,
} as AngieToolMeta,
},
async (args) => { /* handler */ }
);
```
53 changes: 36 additions & 17 deletions example/angie-demo-plugin/src/demo-mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ function createSeoMcpServer() {
}
);

server.tool(
server.registerTool(
'analyze-page-seo',
'Analyzes the SEO of the current page including meta tags, headings, and content structure',
{
url: z.string().describe( 'The URL of the page to analyze' ),
description: 'Analyzes the SEO of the current page including meta tags, headings, and content structure',
inputSchema: {
url: z.string().describe( 'The URL of the page to analyze' ),
},
annotations: {
readOnlyHint: true,
},
},
async ( { url }: { url: string } ) => {
const response = await makeApiRequest( 'angie-demo/v1/analyze-page-seo', { url } );
Expand All @@ -72,14 +77,17 @@ function createSeoMcpServer() {
text: JSON.stringify( response, null, 2 ),
} ],
};
} );
}
);

server.tool(
server.registerTool(
'manage-post-types',
'Manages post types with Angie',
{
postType: z.string().describe( 'The post type to register' ),
action: z.enum( [ 'register', 'unregister' ] ).describe( 'The action to perform' ),
description: 'Manages post types with Angie',
inputSchema: {
postType: z.string().describe( 'The post type to register' ),
action: z.enum( [ 'register', 'unregister' ] ).describe( 'The action to perform' ),
},
},
async ( { postType, action }: { postType: string, action: string } ) => {
const response = await makeApiRequest( 'angie-demo/v1/post-types', { postType, action } );
Expand All @@ -89,12 +97,18 @@ function createSeoMcpServer() {
text: JSON.stringify( response, null, 2 ),
} ],
};
} );
}
);

server.tool(
server.registerTool(
'security-check',
'Checks the security of current WordPress installation',
{},
{
description: 'Checks the security of current WordPress installation',
inputSchema: {},
annotations: {
readOnlyHint: true,
},
},
async () => {
const response = await makeApiRequest( 'angie-demo/v1/security-check', {} );
return {
Expand All @@ -103,11 +117,15 @@ function createSeoMcpServer() {
text: JSON.stringify( response, null, 2 ),
} ],
};
} );
}
);

server.tool( 'run-fireworks',
'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.',
{},
server.registerTool(
'run-fireworks',
{
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.',
inputSchema: {},
},
async () => {
try {
// Create canvas element if it doesn't exist
Expand Down Expand Up @@ -161,7 +179,8 @@ function createSeoMcpServer() {
} ],
};
}
} );
}
);

return server;
}
Expand Down
24 changes: 24 additions & 0 deletions src/angie-annotations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
ANGIE_MODEL_PREFERENCES,
ANGIE_EXTENDED_TIMEOUT,
MCP_READONLY,
AngieToolMeta,
AngieToolAnnotations,
} from './angie-annotations';

describe('angie-annotations', () => {
Expand All @@ -22,4 +24,26 @@ describe('angie-annotations', () => {
it('MCP_READONLY has the correct value', () => {
expect(MCP_READONLY).toBe('readOnlyHint');
});

it('AngieToolMeta interface accepts valid custom metadata', () => {
const meta: AngieToolMeta = {
[ANGIE_REQUIRED_RESOURCES]: [{ uri: 'resource://test', whenToUse: 'always' }],
[ANGIE_MODEL_PREFERENCES]: { intelligencePriority: 0.8 },
[ANGIE_EXTENDED_TIMEOUT]: { timeoutMs: 60000 },
};
expect(meta[ANGIE_REQUIRED_RESOURCES]).toHaveLength(1);
expect(meta[ANGIE_MODEL_PREFERENCES]?.intelligencePriority).toBe(0.8);
expect(meta[ANGIE_EXTENDED_TIMEOUT]?.timeoutMs).toBe(60000);
});

it('AngieToolAnnotations interface accepts standard MCP annotations', () => {
const annotations: AngieToolAnnotations = {
title: 'My Tool',
readOnlyHint: true,
destructiveHint: false,
};
expect(annotations.title).toBe('My Tool');
expect(annotations.readOnlyHint).toBe(true);
expect(annotations.destructiveHint).toBe(false);
});
});
Loading