Skip to main content
Glama
note-mcp-server-http.ts48.5 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import http from "http"; // 設定とユーティリティ import { env, authStatus } from "./config/environment.js"; import { loginToNote } from "./utils/auth.js"; import { noteApiRequest } from "./utils/api-client.js"; import { buildAuthHeaders, hasAuth } from "./utils/auth.js"; // ツールとプロンプトの登録 import { registerAllTools } from "./tools/index.js"; import { registerPrompts } from "./prompts/prompts.js"; // 下書き保存用のカスタムヘッダーを構築 function buildCustomHeaders(): { [key: string]: string } { const headers = buildAuthHeaders(); headers["content-type"] = "application/json"; headers["origin"] = "https://editor.note.com"; headers["referer"] = "https://editor.note.com/"; headers["x-requested-with"] = "XMLHttpRequest"; return headers; } // MCPセッション管理 const sessions = new Map<string, any>(); // ツールリストを取得する関数 async function getToolsList() { // 実際のMCPサーバーからツールリストを取得 return [ { name: "search-notes", description: "note.comの記事を検索(新着順・人気順・急上昇でソート可能)", inputSchema: { type: "object", properties: { query: { type: "string", description: "検索キーワード" }, size: { type: "number", description: "取得件数(1-100)", default: 10 }, sort: { type: "string", description: "ソート順(new/created/like)", default: "new" } }, required: ["query"] } }, { name: "get-note", description: "note.comの記事詳細を取得(下書きも取得可能)", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID(例: n4f0c7b884789)" } }, required: ["noteId"] } }, { name: "analyze-notes", description: "note.comの記事を分析(エンゲージメント、コンテンツ、価格分析)", inputSchema: { type: "object", properties: { noteIds: { type: "array", items: { type: "string" }, description: "分析対象の記事IDリスト" }, analysisType: { type: "string", description: "分析タイプ(engagement/content/price/all)", default: "all" } }, required: ["noteIds"] } }, { name: "search-users", description: "note.comのユーザーを検索", inputSchema: { type: "object", properties: { query: { type: "string", description: "検索キーワード" }, size: { type: "number", description: "取得件数", default: 10 } }, required: ["query"] } }, { name: "get-user", description: "note.comのユーザー詳細を取得", inputSchema: { type: "object", properties: { userId: { type: "string", description: "ユーザーID" } }, required: ["userId"] } }, { name: "get-user-notes", description: "note.comのユーザーの記事一覧を取得", inputSchema: { type: "object", properties: { userId: { type: "string", description: "ユーザーID" }, size: { type: "number", description: "取得件数", default: 10 } }, required: ["userId"] } }, { name: "post-draft-note", description: "note.comに下書き記事を投稿", inputSchema: { type: "object", properties: { title: { type: "string", description: "記事タイトル" }, body: { type: "string", description: "記事本文" }, tags: { type: "array", items: { type: "string" }, description: "タグ(最大10個)" }, id: { type: "string", description: "既存の下書きID(更新する場合)" } }, required: ["title", "body"] } }, { name: "edit-note", description: "既存の記事を編集する", inputSchema: { type: "object", properties: { id: { type: "string", description: "記事ID" }, title: { type: "string", description: "記事タイトル" }, body: { type: "string", description: "記事本文" }, tags: { type: "array", items: { type: "string" }, description: "タグ(最大10個)" }, isDraft: { type: "boolean", description: "下書き状態", default: true } }, required: ["id", "title", "body"] } }, { name: "get-my-notes", description: "自分の記事一覧を取得(下書き含む)", inputSchema: { type: "object", properties: { size: { type: "number", description: "取得件数", default: 10 }, includeDrafts: { type: "boolean", description: "下書きを含める", default: true } }, required: [] } }, { name: "get-comments", description: "記事のコメント一覧を取得", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID" }, size: { type: "number", description: "取得件数", default: 10 } }, required: ["noteId"] } }, { name: "post-comment", description: "記事にコメントを投稿", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID" }, comment: { type: "string", description: "コメント内容" } }, required: ["noteId", "comment"] } }, { name: "like-note", description: "記事にスキをつける", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID" } }, required: ["noteId"] } }, { name: "unlike-note", description: "記事のスキを削除", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID" } }, required: ["noteId"] } }, { name: "search-magazines", description: "note.comのマガジンを検索", inputSchema: { type: "object", properties: { query: { type: "string", description: "検索キーワード" }, size: { type: "number", description: "取得件数", default: 10 } }, required: ["query"] } }, { name: "get-magazine", description: "note.comのマガジン詳細を取得", inputSchema: { type: "object", properties: { magazineId: { type: "string", description: "マガジンID" } }, required: ["magazineId"] } }, { name: "list-categories", description: "note.comのカテゴリー一覧を取得", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "list-hashtags", description: "note.comのハッシュタグ一覧を取得", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get-stats", description: "記事のPV統計情報を取得", inputSchema: { type: "object", properties: { noteId: { type: "string", description: "記事ID" } }, required: ["noteId"] } }, { name: "get-membership-summaries", description: "加入しているメンバーシップ一覧を取得", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get-membership-plans", description: "自分のメンバーシッププラン一覧を取得", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "get-membership-notes", description: "メンバーシップ記事一覧を取得", inputSchema: { type: "object", properties: { size: { type: "number", description: "取得件数", default: 10 } }, required: [] } }, { name: "get-circle-info", description: "サークル情報を取得", inputSchema: { type: "object", properties: { circleId: { type: "string", description: "サークルID" } }, required: ["circleId"] } }, { name: "get-notice-counts", description: "通知件数を取得", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "search-all", description: "note.com全体を検索(記事、ユーザー、ハッシュタグ)", inputSchema: { type: "object", properties: { query: { type: "string", description: "検索キーワード" }, size: { type: "number", description: "取得件数", default: 10 }, sort: { type: "string", description: "ソート順", default: "new" } }, required: ["query"] } } ]; } /** * ◤◢◤◢◤◢◤◢◤◢◤◢◤◢ * note API MCP Server (HTTP/SSE Transport) * * Streamable HTTPトランスポート対応版 * - Cursor、ChatGPT、OpenAI Responses APIからリモート接続可能 * - SSE (Server-Sent Events) によるストリーミング対応 * - HTTP越しのMCP通信をサポート * ◤◢◤◢◤◢◤◢◤◢◤◢◤◢ */ // 環境変数から設定を取得 const PORT = parseInt(env.MCP_HTTP_PORT || "3000", 10); const HOST = env.MCP_HTTP_HOST || "127.0.0.1"; // MCP サーバーインスタンスを作成 const server = new McpServer({ name: "note-api", version: "2.0.0-http" }); /** * サーバーの初期化処理 */ async function initializeServer(): Promise<void> { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("🚀 note API MCP Server v2.0.0 (HTTP) を初期化中..."); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); // ツールの登録 console.error("📝 ツールを登録中..."); registerAllTools(server); // プロンプトの登録 console.error("💭 プロンプトを登録中..."); registerPrompts(server); console.error("✅ ツールとプロンプトの登録が完了しました"); } /** * 認証処理の実行 */ async function performAuthentication(): Promise<void> { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("🔐 認証処理を実行中..."); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); // 自動ログインの試行 if (env.NOTE_EMAIL && env.NOTE_PASSWORD) { console.error("📧 メールアドレスとパスワードからログイン試行中..."); const loginSuccess = await loginToNote(); if (loginSuccess) { console.error("✅ ログイン成功: セッションCookieを取得しました"); } else { console.error("❌ ログイン失敗: メールアドレスまたはパスワードが正しくない可能性があります"); } } // 認証状態の表示 console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); if (authStatus.hasCookie || authStatus.anyAuth) { console.error("🔓 認証情報が設定されています"); console.error("✨ 認証が必要な機能も利用できます"); } else { console.error("⚠️ 警告: 認証情報が設定されていません"); console.error("👀 読み取り機能のみ利用可能です"); console.error("📝 投稿、コメント、スキなどの機能を使うには.envファイルに認証情報を設定してください"); } console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); } /** * HTTPサーバーの起動 */ async function startServer(): Promise<void> { try { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("🌟 note API MCP Server v2.0.0 (HTTP) を起動中..."); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); // サーバーの初期化 await initializeServer(); // 認証処理 await performAuthentication(); // HTTPサーバーを作成 const httpServer = http.createServer(async (req, res) => { // CORSヘッダーを設定 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); // プリフライトリクエストへの対応 if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } // ヘルスチェックエンドポイント if (req.url === "/health" || req.url === "/") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", server: "note-api-mcp", version: "2.0.0-http", transport: "SSE", authenticated: authStatus.hasCookie || authStatus.anyAuth })); return; } // MCPエンドポイント if (req.url?.startsWith("/mcp") || req.url?.startsWith("/sse")) { console.error(`📡 新しいMCP接続: ${req.socket.remoteAddress}`); // POSTリクエストの場合はJSON-RPCを処理 if (req.method === "POST") { let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", async () => { try { const message = JSON.parse(body); console.error("📨 受信JSON-RPC:", message.method); // JSON-RPCレスポンスヘッダー res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }); // initializeリクエストを処理 if (message.method === "initialize") { const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; sessions.set(sessionId, { initialized: true }); const response = { jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: { tools: {}, prompts: {}, resources: {} }, serverInfo: { name: "note-api-mcp", version: "2.0.0-http" } } }; res.end(JSON.stringify(response)); console.error("✅ Initializeレスポンスを送信しました"); return; } // tools/listリクエストを処理 if (message.method === "tools/list") { const toolsList = await getToolsList(); const response = { jsonrpc: "2.0", id: message.id, result: { tools: toolsList } }; res.end(JSON.stringify(response)); console.error(`✅ Tools listレスポンスを送信しました (${toolsList.length}ツール)`); return; } // tools/callリクエストを処理 if (message.method === "tools/call") { try { const { name, arguments: args } = message.params; console.error(`🔧 ツール実行リクエスト: ${name}`, args); // 実際のMCPサーバーからツールを実行 // ここでは実際のnote APIを呼び出す let result; if (name === "search-notes") { // search-notesツールの実装 const { query, size = 10, sort = "hot" } = args; // note APIを呼び出し(正しいエンドポイント) const searchUrl = `/v3/searches?context=note&q=${encodeURIComponent(query)}&size=${size}&start=0&sort=${sort}`; const data = await noteApiRequest(searchUrl, "GET", null, true); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-note") { // get-noteツールの実装 const { noteId } = args; const params = new URLSearchParams({ draft: "true", draft_reedit: "false", ts: Date.now().toString() }); const data = await noteApiRequest( `/v3/notes/${noteId}?${params.toString()}`, "GET", null, true ); // 結果を見やすく整形 const noteData = data.data || {}; const formattedNote = { id: noteData.id || "", title: noteData.name || "", body: noteData.body || "", user: { id: noteData.user?.id || "", name: noteData.user?.nickname || "", urlname: noteData.user?.urlname || "", bio: noteData.user?.bio || "", }, publishedAt: noteData.publishAt || "", likesCount: noteData.likeCount || 0, commentsCount: noteData.commentsCount || 0, status: noteData.status || "", url: `https://note.com/${noteData.user?.urlname || 'unknown'}/n/${noteData.key || ''}` }; result = { content: [{ type: "text", text: JSON.stringify(formattedNote, null, 2) }] }; } else if (name === "get-my-notes") { // get-my-notesツールの実装 const { page = 1, perPage = 20, status = "all" } = args; const params = new URLSearchParams({ page: page.toString(), per_page: perPage.toString(), draft: "true", draft_reedit: "false", ts: Date.now().toString() }); if (status === "draft") { params.set("status", "draft"); } else if (status === "public") { params.set("status", "public"); } const data = await noteApiRequest( `/v2/note_list/contents?${params.toString()}`, "GET", null, true ); let formattedNotes: any[] = []; let totalCount = 0; if (data.data && data.data.notes && Array.isArray(data.data.notes)) { formattedNotes = data.data.notes.map((note: any) => { const isDraft = note.status === "draft"; const noteKey = note.key || ""; const noteId = note.id || ""; const draftTitle = note.noteDraft?.name || ""; const title = note.name || draftTitle || "(無題)"; let excerpt = ""; if (note.body) { excerpt = note.body.length > 100 ? note.body.substring(0, 100) + '...' : note.body; } else if (note.peekBody) { excerpt = note.peekBody; } else if (note.noteDraft?.body) { const textContent = note.noteDraft.body.replace(/<[^>]*>/g, ''); excerpt = textContent.length > 100 ? textContent.substring(0, 100) + '...' : textContent; } const publishedAt = note.publishAt || note.publish_at || note.displayDate || note.createdAt || '日付不明'; return { id: noteId, key: noteKey, title: title, excerpt: excerpt, publishedAt: publishedAt, likesCount: note.likeCount || 0, commentsCount: note.commentsCount || 0, status: note.status || "unknown", isDraft: isDraft, format: note.format || "", url: `https://note.com/***USERNAME_REMOVED***/n/${noteKey}`, editUrl: `https://note.com/***USERNAME_REMOVED***/n/${noteKey}/edit`, hasDraftContent: note.noteDraft ? true : false, lastUpdated: note.noteDraft?.updatedAt || note.createdAt || "", user: { id: note.user?.id || 3647265, name: note.user?.name || note.user?.nickname || "", urlname: note.user?.urlname || "***USERNAME_REMOVED***" } }; }); } totalCount = data.data?.totalCount || 0; const resultData = { total: totalCount, page: page, perPage: perPage, status: status, totalPages: Math.ceil(totalCount / perPage), hasNextPage: page * perPage < totalCount, hasPreviousPage: page > 1, draftCount: formattedNotes.filter((note: any) => note.isDraft).length, publicCount: formattedNotes.filter((note: any) => !note.isDraft).length, notes: formattedNotes }; result = { content: [{ type: "text", text: JSON.stringify(resultData, null, 2) }] }; } else if (name === "get-comments") { // get-commentsツールの実装 const { noteId, size = 10 } = args; const data = await noteApiRequest( `/v1/note/${noteId}/comments?size=${size}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "post-comment") { // post-commentツールの実装 const { noteId, comment } = args; const data = await noteApiRequest( `/v1/note/${noteId}/comments`, "POST", { comment: comment }, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "like-note") { // like-noteツールの実装 const { noteId } = args; const data = await noteApiRequest( `/v3/notes/${noteId}/like`, "POST", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "unlike-note") { // unlike-noteツールの実装 const { noteId } = args; const data = await noteApiRequest( `/v3/notes/${noteId}/unlike`, "POST", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "search-users") { // search-usersツールの実装 const { query, size = 10 } = args; const data = await noteApiRequest( `/v3/searches?context=user&q=${encodeURIComponent(query)}&size=${size}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-user") { // get-userツールの実装 const { username } = args; const data = await noteApiRequest( `/v2/creators/${username}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-user-notes") { // get-user-notesツールの実装 const { username, page = 1 } = args; const data = await noteApiRequest( `/v2/creators/${username}/contents?kind=note&page=${page}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "post-draft-note") { // post-draft-noteツールの実装(11月8日成功版:2段階プロセス) let { title, body, tags = [], id } = args; // 新規作成の場合、まず空の下書きを作成 if (!id) { console.error("新規下書きを作成します..."); const createData = { body: "<p></p>", body_length: 0, name: title || "無題", index: false, is_lead_form: false }; const headers = buildCustomHeaders(); const createResult = await noteApiRequest( "/v1/text_notes", "POST", createData, true, headers ); if (createResult.data?.id) { id = createResult.data.id.toString(); console.error(`下書き作成成功: ID=${id}`); } else { throw new Error("下書きの作成に失敗しました"); } } // 下書きを更新 console.error(`下書きを更新します (ID: ${id})`); const updateData = { body: body, body_length: body.length, name: title || "無題", index: false, is_lead_form: false }; const headers = await buildCustomHeaders(); const data = await noteApiRequest( `/v1/text_notes/draft_save?id=${id}&is_temp_saved=true`, "POST", updateData, true, headers ); result = { content: [{ type: "text", text: JSON.stringify({ success: true, message: "記事を下書き保存しました", noteId: id, editUrl: `https://editor.note.com/notes/${id}`, data: data }, null, 2) }] }; } else if (name === "edit-note") { // edit-noteツールの実装(参考: https://note.com/taku_sid/n/n1b1b7894e28f) const { id, title, body, tags = [], isDraft = true } = args; // 参照記事に基づく正しいパラメータ形式 const postData = { name: title, // 'title'ではなく'name' body: body, status: isDraft ? "draft" : "published" }; const data = await noteApiRequest( `/v1/text_notes/${id}`, "PUT", postData, true ); result = { content: [{ type: "text", text: JSON.stringify({ success: true, message: "記事を更新しました", data: data }, null, 2) }] }; } else if (name === "search-magazines") { // search-magazinesツールの実装 const { query, size = 10 } = args; const data = await noteApiRequest( `/v3/searches?context=magazine&q=${encodeURIComponent(query)}&size=${size}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-magazine") { // get-magazineツールの実装 const { magazineId } = args; const data = await noteApiRequest( `/v1/magazines/${magazineId}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "list-categories") { // list-categoriesツールの実装 const data = await noteApiRequest( `/v2/categories`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "list-hashtags") { // list-hashtagsツールの実装 const data = await noteApiRequest( `/v2/hashtags`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-stats") { // get-statsツールの実装 const { noteId } = args; const data = await noteApiRequest( `/v1/notes/${noteId}/stats`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-membership-summaries") { // get-membership-summariesツールの実装 const data = await noteApiRequest( `/v1/memberships/summaries`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-membership-plans") { // get-membership-plansツールの実装 const data = await noteApiRequest( `/v1/users/me/membership_plans`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-membership-notes") { // get-membership-notesツールの実装 const { size = 10 } = args; const data = await noteApiRequest( `/v1/memberships/notes?size=${size}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-circle-info") { // get-circle-infoツールの実装 const { circleId } = args; const data = await noteApiRequest( `/v1/circles/${circleId}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "get-notice-counts") { // get-notice-countsツールの実装 const data = await noteApiRequest( `/v3/notice_counts`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "search-all") { // search-allツールの実装 const { query, size = 10, sort = "new" } = args; const data = await noteApiRequest( `/v3/searches?context=all&q=${encodeURIComponent(query)}&size=${size}&sort=${sort}`, "GET", null, true ); result = { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } else if (name === "analyze-notes") { // analyze-notesツールの実装 const { noteIds, analysisType = "all" } = args; // 分析結果のモック(実際のAPIがないため) const analysisResult = { analysisType: analysisType, noteIds: noteIds, engagement: { totalLikes: 0, totalComments: 0, averageLikesPerNote: 0 }, content: { averageBodyLength: 0, hasImages: false, hasTags: false }, price: { paidNotesCount: 0, freeNotesCount: noteIds.length, averagePrice: 0 } }; result = { content: [{ type: "text", text: JSON.stringify(analysisResult, null, 2) }] }; } else { // その他のツールは未実装 result = { content: [{ type: "text", text: `ツール '${name}' はまだHTTPトランスポートで実装されていません。stdioトランスポートで利用してください。` }] }; } const response = { jsonrpc: "2.0", id: message.id, result: result }; res.end(JSON.stringify(response)); console.error(`✅ ツール実行完了: ${name}`); return; } catch (error) { console.error(`❌ ツール実行エラー:`, error); const response = { jsonrpc: "2.0", id: message.id, error: { code: -32603, message: "Tool execution error", data: error instanceof Error ? error.message : String(error) } }; res.end(JSON.stringify(response)); return; } } // その他のメソッド const response = { jsonrpc: "2.0", id: message.id, error: { code: -32601, message: "Method not found" } }; res.end(JSON.stringify(response)); console.error("⚠️ 未対応のメソッド:", message.method); } catch (error) { console.error("❌ JSON-RPC処理エラー:", error); res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" } })); } }); return; } // GETリクエストの場合はSSEストリームを開始 if (req.method === "GET") { try { const transport = new SSEServerTransport("/mcp", res); await server.connect(transport); console.error("✅ SSE接続が確立されました"); req.on("close", () => { console.error("🔌 SSE接続が閉じられました"); }); } catch (error) { console.error("❌ SSE接続エラー:", error); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "SSE connection failed" })); } return; } return; } // 404エラー res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not Found", message: "利用可能なエンドポイント: /health, /mcp, /sse" })); }); // サーバーを起動 httpServer.listen(PORT, HOST, () => { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("🎉 note API MCP Server v2.0.0 (HTTP) が正常に起動しました!"); console.error(`📡 HTTP/SSE transport で稼働中: http://${HOST}:${PORT}`); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("\n🔗 接続方法:"); console.error(` ヘルスチェック: http://${HOST}:${PORT}/health`); console.error(` MCPエンドポイント: http://${HOST}:${PORT}/mcp`); console.error(` SSEエンドポイント: http://${HOST}:${PORT}/sse`); console.error("\n📋 利用可能な機能:"); console.error("🔍 検索機能:"); console.error(" - search-notes: 記事検索"); console.error(" - analyze-notes: 記事分析"); console.error(" - search-users: ユーザー検索"); console.error(" - search-magazines: マガジン検索"); console.error(" - search-all: 全体検索"); console.error("\n📝 記事機能:"); console.error(" - get-note: 記事詳細取得"); console.error(" - post-draft-note: 下書き投稿"); console.error(" - get-comments: コメント取得"); console.error(" - post-comment: コメント投稿"); console.error(" - like-note / unlike-note: スキ操作"); console.error(" - get-my-notes: 自分の記事一覧"); console.error("\n👥 ユーザー機能:"); console.error(" - get-user: ユーザー詳細取得"); console.error(" - get-user-notes: ユーザーの記事一覧"); console.error(" - get-stats: PV統計取得"); console.error("\n🎪 メンバーシップ機能:"); console.error(" - get-membership-summaries: 加入メンバーシップ一覧"); console.error(" - get-membership-plans: 自分のプラン一覧"); console.error(" - get-membership-notes: メンバーシップ記事一覧"); console.error(" - get-circle-info: サークル情報取得"); console.error("\n📚 その他機能:"); console.error(" - get-magazine: マガジン詳細取得"); console.error(" - list-categories: カテゴリー一覧"); console.error(" - list-hashtags: ハッシュタグ一覧"); console.error(" - get-notice-counts: 通知件数"); console.error("\n💭 プロンプト:"); console.error(" - note-search: 記事検索プロンプト"); console.error(" - competitor-analysis: 競合分析"); console.error(" - content-idea-generation: アイデア生成"); console.error(" - article-analysis: 記事分析"); console.error(" - membership-strategy: メンバーシップ戦略"); console.error(" - content-calendar: コンテンツカレンダー"); console.error("\n◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("🎯 Ready for HTTP/SSE connections!"); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); }); // エラーハンドリング httpServer.on("error", (error: NodeJS.ErrnoException) => { if (error.code === "EADDRINUSE") { console.error(`❌ ポート ${PORT} は既に使用されています`); console.error("別のポートを使用するには、環境変数 MCP_HTTP_PORT を設定してください"); } else { console.error("❌ HTTPサーバーエラー:", error); } process.exit(1); }); } catch (error) { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("💥 Fatal error during server startup:"); console.error(error); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); process.exit(1); } } // メイン処理の実行 startServer().catch(error => { console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); console.error("💥 Fatal error:"); console.error(error); console.error("◤◢◤◢◤◢◤◢◤◢◤◢◤◢"); process.exit(1); }); // ファイル情報の表示(開発用) if (env.DEBUG) { console.error("📂 HTTP Transport 情報:"); console.error(`🌐 ホスト: ${HOST}`); console.error(`🔌 ポート: ${PORT}`); console.error("📡 トランスポート: SSE (Server-Sent Events)"); console.error("🔗 プロトコル: HTTP/1.1"); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shimayuz/note-com-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server