원클릭으로
mcp-development
MCPサーバー開発を支援します。プロトコル準拠、Pydanticスキーマ設計、Playwright統合のベストプラクティスを提供します。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
MCPサーバー開発を支援します。プロトコル準拠、Pydanticスキーマ設計、Playwright統合のベストプラクティスを提供します。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
作業進捗をGitHub issueに自動報告します。計画立案時、知見獲得時、問題発覚時にブランチ名からissue番号を抽出し、構造化されたコメントを投稿します。
note.com APIの調査を支援します。mitmproxyとPlaywrightを使用してHTTPトラフィックをキャプチャ・分析し、API動作を解明します。
コード品質基準の完全遵守を保証します。コミット前の品質チェックを自動実行し、エラーがある状態でのコミットを防止します。
プロジェクト憲法への準拠を検証します。実装完了時やPR作成時に全Articleをチェックし、違反がないことを確認します。
コード変更、API変更、新機能追加時にドキュメントを自動更新します。公開APIやインターフェースの変更、新クラス・関数・モジュールの追加、アーキテクチャの重要な変更、ユーザーからの明示的なドキュメント更新依頼時に起動します。プロジェクトのドキュメント標準に準拠した更新を提案します。
TDDワークフローを強制し、テストファースト開発を支援します。新機能実装時に自動起動し、Red-Green-Refactorサイクルを確実に実行します。
| name | mcp-development |
| description | MCPサーバー開発を支援します。プロトコル準拠、Pydanticスキーマ設計、Playwright統合のベストプラクティスを提供します。 |
| allowed-tools | Read Edit Write Glob Grep Bash |
このスキルは、Constitution Article 3: MCP Protocol Compliance を支援し、MCPサーバー開発のベストプラクティスを提供します。
以下の状況で起動します:
MCPツールは以下の構造を持つ必要があります:
from mcp.types import Tool, TextContent
from pydantic import BaseModel, Field
class CreateDraftInput(BaseModel):
"""下書き作成の入力パラメータ"""
title: str = Field(..., description="記事のタイトル")
body: str = Field(..., description="記事の本文(Markdown形式)")
tags: list[str] | None = Field(None, description="記事のタグ一覧")
# ツール定義
create_draft_tool = Tool(
name="create_draft",
description="note.comに記事の下書きを作成します",
inputSchema=CreateDraftInput.model_json_schema(),
)
必須ルール:
from pydantic import BaseModel, Field, field_validator
class ArticleContent(BaseModel):
"""記事コンテンツのスキーマ"""
title: str = Field(..., min_length=1, max_length=200, description="記事タイトル")
body: str = Field(..., min_length=1, description="記事本文")
status: str = Field("draft", pattern="^(draft|published)$", description="記事状態")
@field_validator("title")
@classmethod
def title_not_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("タイトルは空にできません")
return v.strip()
MCPエラーは適切な形式で返す必要があります:
from mcp.types import TextContent, ErrorData
from mcp.shared.exceptions import McpError
class NoteApiError(McpError):
"""note.com API関連のエラー"""
pass
class AuthenticationError(NoteApiError):
"""認証エラー"""
def __init__(self, message: str = "認証が必要です"):
super().__init__(ErrorData(code=-32001, message=message))
class SessionExpiredError(NoteApiError):
"""セッション期限切れエラー"""
def __init__(self):
super().__init__(ErrorData(
code=-32002,
message="セッションの有効期限が切れました。再ログインしてください。"
))
from playwright.async_api import async_playwright, Browser, Page
from contextlib import asynccontextmanager
class BrowserManager:
"""ブラウザインスタンスのライフサイクル管理"""
def __init__(self) -> None:
self._browser: Browser | None = None
self._page: Page | None = None
async def get_page(self) -> Page:
"""既存のページを再利用、なければ新規作成"""
if self._page is not None and not self._page.is_closed():
return self._page
if self._browser is None:
playwright = await async_playwright().start()
self._browser = await playwright.chromium.launch(headless=False)
self._page = await self._browser.new_page()
return self._page
async def close(self) -> None:
"""リソースのクリーンアップ"""
if self._page:
await self._page.close()
if self._browser:
await self._browser.close()
async def show_preview(self, article_id: str) -> None:
"""プレビューを表示(既存ウィンドウを再利用)"""
page = await self.browser_manager.get_page()
# 既にプレビューページにいる場合はリロード
current_url = page.url
preview_url = f"https://note.com/api/v1/text_notes/{article_id}/preview"
if current_url == preview_url:
await page.reload()
else:
await page.goto(preview_url)
OSのキーチェーン/資格情報マネージャーを使用:
import keyring
from dataclasses import dataclass
from datetime import datetime
import json
SERVICE_NAME = "note-mcp"
@dataclass
class Session:
"""セッション情報"""
cookies: dict[str, str]
user_id: str
expires_at: datetime
def is_valid(self) -> bool:
"""セッションが有効かチェック"""
return datetime.now() < self.expires_at
def save_session(session: Session) -> None:
"""セッションをセキュアに保存"""
keyring.set_password(
SERVICE_NAME,
"session",
json.dumps({
"cookies": session.cookies,
"user_id": session.user_id,
"expires_at": session.expires_at.isoformat(),
})
)
def load_session() -> Session | None:
"""保存されたセッションを読み込み"""
data = keyring.get_password(SERVICE_NAME, "session")
if data is None:
return None
parsed = json.loads(data)
session = Session(
cookies=parsed["cookies"],
user_id=parsed["user_id"],
expires_at=datetime.fromisoformat(parsed["expires_at"]),
)
if not session.is_valid():
keyring.delete_password(SERVICE_NAME, "session")
return None
return session
async def execute_with_session(self, operation: Callable) -> Any:
"""セッションを確認して操作を実行"""
session = load_session()
if session is None:
raise AuthenticationError("ログインが必要です")
if not session.is_valid():
raise SessionExpiredError()
try:
return await operation()
except SessionExpiredError:
# セッションをクリアして再認証を促す
keyring.delete_password(SERVICE_NAME, "session")
raise