Add readOnlyHint and openWorldHint annotations to all MCP tools
This fixes ChatGPT's "MCP write action is temporarily disabled" error by explicitly marking all tools as read-only operations. ChatGPT's Developer Mode was misinterpreting the tools as write actions without these annotations.
Changes:
- Added annotations to all 5 tools in both index.js and server-http.js
- All tools now have: annotations: { readOnlyHint: true, openWorldHint: true }
- Added test-annotations.js to verify annotations are correctly returned
Tools updated:
- search_episodes
- get_episode
- search_transcripts
- get_host_info
- get_series_info
🤖 Generated with Claude Code
This commit is contained in:
20
index.js
20
index.js
@@ -290,6 +290,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
{
|
{
|
||||||
name: 'search_episodes',
|
name: 'search_episodes',
|
||||||
description: 'Search HPR episodes by keywords in title, summary, tags, or host notes. Can filter by host, series, tags, and date range.',
|
description: 'Search HPR episodes by keywords in title, summary, tags, or host notes. Can filter by host, series, tags, and date range.',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -328,6 +332,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
{
|
{
|
||||||
name: 'get_episode',
|
name: 'get_episode',
|
||||||
description: 'Get detailed information about a specific HPR episode including transcript if available',
|
description: 'Get detailed information about a specific HPR episode including transcript if available',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -350,6 +358,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
{
|
{
|
||||||
name: 'search_transcripts',
|
name: 'search_transcripts',
|
||||||
description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters',
|
description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -402,6 +414,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
{
|
{
|
||||||
name: 'get_host_info',
|
name: 'get_host_info',
|
||||||
description: 'Get information about an HPR host including all their episodes',
|
description: 'Get information about an HPR host including all their episodes',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -424,6 +440,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
{
|
{
|
||||||
name: 'get_series_info',
|
name: 'get_series_info',
|
||||||
description: 'Get information about an HPR series including all episodes in the series',
|
description: 'Get information about an HPR series including all episodes in the series',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -402,6 +402,10 @@ All content is contributed by the community, for the community.`,
|
|||||||
{
|
{
|
||||||
name: 'search_episodes',
|
name: 'search_episodes',
|
||||||
description: 'Search HPR episodes by keywords in title, summary, tags, or host notes. Can filter by host, series, tags, and date range.',
|
description: 'Search HPR episodes by keywords in title, summary, tags, or host notes. Can filter by host, series, tags, and date range.',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -440,6 +444,10 @@ All content is contributed by the community, for the community.`,
|
|||||||
{
|
{
|
||||||
name: 'get_episode',
|
name: 'get_episode',
|
||||||
description: 'Get detailed information about a specific HPR episode including transcript if available',
|
description: 'Get detailed information about a specific HPR episode including transcript if available',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -462,6 +470,10 @@ All content is contributed by the community, for the community.`,
|
|||||||
{
|
{
|
||||||
name: 'search_transcripts',
|
name: 'search_transcripts',
|
||||||
description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters',
|
description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -514,6 +526,10 @@ All content is contributed by the community, for the community.`,
|
|||||||
{
|
{
|
||||||
name: 'get_host_info',
|
name: 'get_host_info',
|
||||||
description: 'Get information about an HPR host including all their episodes',
|
description: 'Get information about an HPR host including all their episodes',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -536,6 +552,10 @@ All content is contributed by the community, for the community.`,
|
|||||||
{
|
{
|
||||||
name: 'get_series_info',
|
name: 'get_series_info',
|
||||||
description: 'Get information about an HPR series including all episodes in the series',
|
description: 'Get information about an HPR series including all episodes in the series',
|
||||||
|
annotations: {
|
||||||
|
readOnlyHint: true,
|
||||||
|
openWorldHint: true
|
||||||
|
},
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
84
test-annotations.js
Normal file
84
test-annotations.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick test to verify tool annotations are present
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventSource from 'eventsource';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
const SERVER_URL = 'http://localhost:3000';
|
||||||
|
const SSE_ENDPOINT = `${SERVER_URL}/sse`;
|
||||||
|
const MESSAGE_ENDPOINT = `${SERVER_URL}/message`;
|
||||||
|
|
||||||
|
let requestId = 1;
|
||||||
|
let sse;
|
||||||
|
let connectionId = null;
|
||||||
|
|
||||||
|
async function testAnnotations() {
|
||||||
|
console.log('Testing tool annotations...\n');
|
||||||
|
|
||||||
|
// Connect to SSE
|
||||||
|
sse = new EventSource(SSE_ENDPOINT);
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
sse.addEventListener('endpoint', (event) => {
|
||||||
|
const url = new URL(event.data, SERVER_URL);
|
||||||
|
connectionId = url.searchParams.get('sessionId');
|
||||||
|
console.log(`Connected with session ID: ${connectionId}\n`);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait a moment for connection to stabilize
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// Send tools/list request
|
||||||
|
const message = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: requestId++,
|
||||||
|
method: 'tools/list',
|
||||||
|
params: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const messagePromise = new Promise((resolve) => {
|
||||||
|
sse.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.result && data.result.tools) {
|
||||||
|
resolve(data.result.tools);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetch(MESSAGE_ENDPOINT, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-connection-id': connectionId
|
||||||
|
},
|
||||||
|
body: JSON.stringify(message)
|
||||||
|
});
|
||||||
|
|
||||||
|
const tools = await messagePromise;
|
||||||
|
|
||||||
|
console.log('Tool annotations check:\n');
|
||||||
|
tools.forEach(tool => {
|
||||||
|
const hasAnnotations = tool.annotations &&
|
||||||
|
tool.annotations.readOnlyHint === true &&
|
||||||
|
tool.annotations.openWorldHint === true;
|
||||||
|
const status = hasAnnotations ? '✅' : '❌';
|
||||||
|
console.log(`${status} ${tool.name}: annotations = ${JSON.stringify(tool.annotations || 'MISSING')}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
sse.close();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
testAnnotations().catch(err => {
|
||||||
|
console.error('Error:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user