From 020d324edba8c5bab6dfdc9d45b0267059868edd Mon Sep 17 00:00:00 2001 From: Lee Hanken Date: Mon, 27 Oct 2025 14:45:11 +0000 Subject: [PATCH] Add readOnlyHint and openWorldHint annotations to all MCP tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- index.js | 20 +++++++++++ server-http.js | 20 +++++++++++ test-annotations.js | 84 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 test-annotations.js diff --git a/index.js b/index.js index 482d474..26085f2 100755 --- a/index.js +++ b/index.js @@ -290,6 +290,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { 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.', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -328,6 +332,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { name: 'get_episode', description: 'Get detailed information about a specific HPR episode including transcript if available', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -350,6 +358,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { name: 'search_transcripts', description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -402,6 +414,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { name: 'get_host_info', description: 'Get information about an HPR host including all their episodes', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -424,6 +440,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { name: 'get_series_info', description: 'Get information about an HPR series including all episodes in the series', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { diff --git a/server-http.js b/server-http.js index decaf0d..b53ab88 100644 --- a/server-http.js +++ b/server-http.js @@ -402,6 +402,10 @@ All content is contributed by the community, for the community.`, { 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.', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -440,6 +444,10 @@ All content is contributed by the community, for the community.`, { name: 'get_episode', description: 'Get detailed information about a specific HPR episode including transcript if available', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -462,6 +470,10 @@ All content is contributed by the community, for the community.`, { name: 'search_transcripts', description: 'Search through episode transcripts using phrases or multiple terms with AND/OR matching and optional host filters', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -514,6 +526,10 @@ All content is contributed by the community, for the community.`, { name: 'get_host_info', description: 'Get information about an HPR host including all their episodes', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { @@ -536,6 +552,10 @@ All content is contributed by the community, for the community.`, { name: 'get_series_info', description: 'Get information about an HPR series including all episodes in the series', + annotations: { + readOnlyHint: true, + openWorldHint: true + }, inputSchema: { type: 'object', properties: { diff --git a/test-annotations.js b/test-annotations.js new file mode 100644 index 0000000..0c726a7 --- /dev/null +++ b/test-annotations.js @@ -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); +});