upload_skill_draft
Upload a skill in draft mode without an API key. Returns a claim token for tracking status, enabling autonomous agent contributions with human verification.
Instructions
Upload a skill without an API key (zero-friction). Returns a claim_token to track status. Perfect for agents contributing skills autonomously. / API 키 없이 Draft 모드로 스킬 업로드.
▶ 정책:
AI 검수(approved)만 수락, 그 외(caution/pending/rejected)는 즉시 삭제.
결과는 sandbox tier + claim_status='draft'. 사람 owner 가 claim 해야 community/검증 경로 진입.
동일 agent_author 의 모든 Draft 는 한 번의 이메일 인증으로 일괄 claim 됨.
▶ 에이전트 필수 동작(MANDATORY — 무시 시 반복 업로드/인증이 실패): (1) 최초 업로드 응답의 agent_identity.agent_secret 값을 반드시 저장하라. 이후 같은 agent_author 로 업로드할 때 agent_secret 파라미터에 동일 값 전달. 저장/재사용 안 하면 AGENT_SECRET_REQUIRED(401) 로 차단됨. (2) 응답의 claim_url, human_action.instruction, agent_identity 를 사용자(사람)에게 그대로, 링크 형태로 표시하라 (요약·의역 금지). (3) 에이전트가 대신 claim 을 완료하려 하지 말 것. contact_email/verify 메일은 반드시 사람 owner 의 실제 이메일이어야 함. (4) human_action_required=true 이면 사용자 응답을 기다려라 — 자동 재시도 금지.
Args: agent_author: 에이전트 식별자 (X-Agent-Author 헤더로 전송). 예: "claude-sonnet-4-6@anthropic". 같은 이름은 agent_secret 으로만 재사용 가능. skill_md: SKILL.md 전체 내용 문자열 (필수). files: {"main.py": "...", "util.py": "..."} 형태의 부가 파일 dict (선택). requirements: requirements.txt 내용 문자열 (선택). contact_email: 업로더 사람 owner 의 이메일 (선택, OPTIONAL). ▶ 사용자 이메일을 모르면 반드시 비워두세요 — 추측·생성한 가짜 이메일은 DNS resolve 검증(NXDOMAIN 차단)으로 CONTACT_EMAIL_INVALID(400) 거부됩니다. ▶ 비워두면 응답의 claim_url 을 사람 사용자에게 채팅으로 그대로 보여주면 됩니다 (forward_claim_url 시나리오, 권장). ▶ 사용자가 명시적으로 알려준 실제 이메일이 있을 때만 지정. 지정 시 서버가 verify 링크를 자동 발송 (24시간 만료, 미인증 시 72시간마다 최대 3회 reminder). ▶ 한 번만 지정하면 되며 이후 업로드엔 불필요. verify 링크를 사람이 클릭하면 해당 agent_author 의 모든 Draft 가 그 계정으로 일괄 이전. agent_secret: 최초 업로드에서 발급된 secret (2회차 이후 필수). claim_token: 같은 Draft 에 새 버전을 추가할 때만 (선택).
Returns: 업로드 결과 + agent_identity + human_action_required + human_action + claim_url 요약. 사용자에게 claim_url 과 instruction 을 반드시 surface 하라.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| agent_author | Yes | ||
| skill_md | Yes | ||
| files | No | ||
| requirements | No | ||
| contact_email | No | ||
| agent_secret | No | ||
| claim_token | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- mcp_server/skill_store_mcp.py:565-704 (handler)The upload_skill_draft tool implementation. It is decorated with @mcp.tool() and @_log_tool, takes parameters (agent_author, skill_md, files, requirements, contact_email, agent_secret, claim_token), sends a POST request to /v1/drafts/upload with X-Agent-Author and optional X-Agent-Secret headers, and returns a formatted result string including agent identity and human action required info.
@mcp.tool() @_log_tool def upload_skill_draft( agent_author: str, skill_md: str, files: Optional[dict] = None, requirements: Optional[str] = None, contact_email: Optional[str] = None, agent_secret: Optional[str] = None, claim_token: Optional[str] = None, ) -> str: """ Upload a skill without an API key (zero-friction). Returns a claim_token to track status. Perfect for agents contributing skills autonomously. / API 키 없이 Draft 모드로 스킬 업로드. ▶ 정책: - AI 검수(approved)만 수락, 그 외(caution/pending/rejected)는 즉시 삭제. - 결과는 sandbox tier + claim_status='draft'. 사람 owner 가 claim 해야 community/검증 경로 진입. - 동일 agent_author 의 모든 Draft 는 한 번의 이메일 인증으로 일괄 claim 됨. ▶ 에이전트 필수 동작(MANDATORY — 무시 시 반복 업로드/인증이 실패): (1) 최초 업로드 응답의 agent_identity.agent_secret 값을 반드시 저장하라. 이후 같은 agent_author 로 업로드할 때 agent_secret 파라미터에 동일 값 전달. 저장/재사용 안 하면 AGENT_SECRET_REQUIRED(401) 로 차단됨. (2) 응답의 claim_url, human_action.instruction, agent_identity 를 사용자(사람)에게 그대로, 링크 형태로 표시하라 (요약·의역 금지). (3) 에이전트가 대신 claim 을 완료하려 하지 말 것. contact_email/verify 메일은 반드시 사람 owner 의 실제 이메일이어야 함. (4) human_action_required=true 이면 사용자 응답을 기다려라 — 자동 재시도 금지. Args: agent_author: 에이전트 식별자 (X-Agent-Author 헤더로 전송). 예: "claude-sonnet-4-6@anthropic". 같은 이름은 agent_secret 으로만 재사용 가능. skill_md: SKILL.md 전체 내용 문자열 (필수). files: {"main.py": "...", "util.py": "..."} 형태의 부가 파일 dict (선택). requirements: requirements.txt 내용 문자열 (선택). contact_email: 업로더 사람 owner 의 이메일 (선택, OPTIONAL). ▶ **사용자 이메일을 모르면 반드시 비워두세요** — 추측·생성한 가짜 이메일은 DNS resolve 검증(NXDOMAIN 차단)으로 CONTACT_EMAIL_INVALID(400) 거부됩니다. ▶ 비워두면 응답의 claim_url 을 사람 사용자에게 채팅으로 그대로 보여주면 됩니다 (forward_claim_url 시나리오, 권장). ▶ 사용자가 명시적으로 알려준 실제 이메일이 있을 때만 지정. 지정 시 서버가 verify 링크를 자동 발송 (24시간 만료, 미인증 시 72시간마다 최대 3회 reminder). ▶ 한 번만 지정하면 되며 이후 업로드엔 불필요. verify 링크를 사람이 클릭하면 해당 agent_author 의 모든 Draft 가 그 계정으로 일괄 이전. agent_secret: 최초 업로드에서 발급된 secret (2회차 이후 필수). claim_token: 같은 Draft 에 새 버전을 추가할 때만 (선택). Returns: 업로드 결과 + agent_identity + human_action_required + human_action + claim_url 요약. 사용자에게 claim_url 과 instruction 을 반드시 surface 하라. """ url = f"{SKILL_STORE_URL}/v1/drafts/upload" if not agent_author or not agent_author.strip(): return "❌ agent_author 는 필수입니다." if not skill_md or not skill_md.strip(): return "❌ skill_md 는 필수입니다." payload: dict = {"skill_md": skill_md} if files: if not isinstance(files, dict): return "❌ files 는 {파일명: 내용} 딕셔너리여야 합니다." payload["files"] = files if requirements: payload["requirements"] = requirements if contact_email: payload["contact_email"] = contact_email if claim_token: payload["claim_token"] = claim_token headers = { "Content-Type": "application/json", "X-Agent-Author": agent_author.strip(), } if agent_secret: headers["X-Agent-Secret"] = agent_secret.strip() body = json.dumps(payload).encode("utf-8") req = urllib.request.Request(url, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(req, timeout=30) as resp: data = json.loads(resp.read().decode()) except urllib.error.HTTPError as e: body_msg = e.read().decode() try: err = json.loads(body_msg) code = err.get("error_code") or err.get("title") or "HTTP_ERROR" msg = err.get("detail") or err.get("message") or body_msg hint = "" if code in ("AGENT_SECRET_REQUIRED", "AGENT_SECRET_INVALID"): hint = ("\n⚠️ 최초 업로드 응답의 agent_identity.agent_secret 을 반드시 " "재사용하세요. 저장된 값이 없다면 다른 agent_author 를 사용해야 합니다.") return f"❌ Draft 업로드 실패 [{code}]: {msg}{hint}" except Exception: return f"❌ Draft 업로드 실패: {body_msg}" except Exception as e: return f"❌ 오류: {str(e)}" ident = data.get("agent_identity") or {} human = data.get("human_action") or {} lines = [ f"✅ Draft 업로드 성공 — {data.get('skill_name')} v{data.get('version_number')}", f"skill_id: {data.get('skill_id')}", f"trust_level: {data.get('trust_level')} (claim 전까지)", f"claim_token: {data.get('claim_token')}", f"claim_url: {data.get('claim_url')}", f"expires_at: {data.get('expires_at')}", ] if data.get("vetting_job_id"): lines.append(f"vetting_job_id: {data.get('vetting_job_id')} " f"(폴링: get_vetting_result)") # Agent Identity block lines.append("") lines.append("── Agent Identity ──") lines.append(f" is_new: {ident.get('is_new')}") if ident.get("agent_secret"): lines.append(f" agent_secret: {ident['agent_secret']}") lines.append(" ⚠️ 이 값을 반드시 저장하라. 같은 agent_author 의 다음 업로드부터 agent_secret 파라미터에 그대로 사용.") lines.append(f" contact_email: {ident.get('contact_email') or '(없음)'}") lines.append(f" contact_email_verified: {ident.get('contact_email_verified')}") lines.append(f" claimed: {ident.get('claimed')}") lines.append(f" verify_email_sent: {ident.get('verify_email_sent')}") # Human action surface (D1) — MUST show to user verbatim if data.get("human_action_required"): lines.append("") lines.append("── ⚠️ HUMAN ACTION REQUIRED (사용자에게 그대로 표시) ──") lines.append(f" action: {human.get('type')}") lines.append(f" deadline: {human.get('deadline')}") if human.get("contact_email"): lines.append(f" email: {human['contact_email']}") lines.append(f" claim_url: {human.get('claim_url')}") lines.append(f" instruction: {human.get('instruction')}") lines.append("") lines.append(" ℹ️ 위 claim_url 을 사용자에게 링크 형태로 그대로 전달하세요. " "에이전트가 대신 claim 을 완료해서는 안 됩니다.") return "\n".join(lines) - The type annotations and docstring for upload_skill_draft define the input schema: agent_author (str, required), skill_md (str, required), files (Optional[dict]), requirements (Optional[str]), contact_email (Optional[str]), agent_secret (Optional[str]), claim_token (Optional[str]). Returns str.
def upload_skill_draft( agent_author: str, skill_md: str, files: Optional[dict] = None, requirements: Optional[str] = None, contact_email: Optional[str] = None, agent_secret: Optional[str] = None, claim_token: Optional[str] = None, ) -> str: """ Upload a skill without an API key (zero-friction). Returns a claim_token to track status. Perfect for agents contributing skills autonomously. / API 키 없이 Draft 모드로 스킬 업로드. ▶ 정책: - AI 검수(approved)만 수락, 그 외(caution/pending/rejected)는 즉시 삭제. - 결과는 sandbox tier + claim_status='draft'. 사람 owner 가 claim 해야 community/검증 경로 진입. - 동일 agent_author 의 모든 Draft 는 한 번의 이메일 인증으로 일괄 claim 됨. ▶ 에이전트 필수 동작(MANDATORY — 무시 시 반복 업로드/인증이 실패): (1) 최초 업로드 응답의 agent_identity.agent_secret 값을 반드시 저장하라. 이후 같은 agent_author 로 업로드할 때 agent_secret 파라미터에 동일 값 전달. 저장/재사용 안 하면 AGENT_SECRET_REQUIRED(401) 로 차단됨. (2) 응답의 claim_url, human_action.instruction, agent_identity 를 사용자(사람)에게 그대로, 링크 형태로 표시하라 (요약·의역 금지). (3) 에이전트가 대신 claim 을 완료하려 하지 말 것. contact_email/verify 메일은 반드시 사람 owner 의 실제 이메일이어야 함. (4) human_action_required=true 이면 사용자 응답을 기다려라 — 자동 재시도 금지. Args: agent_author: 에이전트 식별자 (X-Agent-Author 헤더로 전송). 예: "claude-sonnet-4-6@anthropic". 같은 이름은 agent_secret 으로만 재사용 가능. skill_md: SKILL.md 전체 내용 문자열 (필수). files: {"main.py": "...", "util.py": "..."} 형태의 부가 파일 dict (선택). requirements: requirements.txt 내용 문자열 (선택). contact_email: 업로더 사람 owner 의 이메일 (선택, OPTIONAL). ▶ **사용자 이메일을 모르면 반드시 비워두세요** — 추측·생성한 가짜 이메일은 DNS resolve 검증(NXDOMAIN 차단)으로 CONTACT_EMAIL_INVALID(400) 거부됩니다. ▶ 비워두면 응답의 claim_url 을 사람 사용자에게 채팅으로 그대로 보여주면 됩니다 (forward_claim_url 시나리오, 권장). ▶ 사용자가 명시적으로 알려준 실제 이메일이 있을 때만 지정. 지정 시 서버가 verify 링크를 자동 발송 (24시간 만료, 미인증 시 72시간마다 최대 3회 reminder). ▶ 한 번만 지정하면 되며 이후 업로드엔 불필요. verify 링크를 사람이 클릭하면 해당 agent_author 의 모든 Draft 가 그 계정으로 일괄 이전. agent_secret: 최초 업로드에서 발급된 secret (2회차 이후 필수). claim_token: 같은 Draft 에 새 버전을 추가할 때만 (선택). Returns: 업로드 결과 + agent_identity + human_action_required + human_action + claim_url 요약. 사용자에게 claim_url 과 instruction 을 반드시 surface 하라. """ - mcp_server/skill_store_mcp.py:565-566 (registration)The tool is registered via the @mcp.tool() decorator on a FastMCP instance named 'skill-store'. This is the MCP framework registration mechanism.
@mcp.tool() @_log_tool - mcp_server/skill_store_mcp.py:33-43 (helper)The _log_tool decorator used on upload_skill_draft (and other tools) for logging each tool call to stdout.
def _log_tool(fn): """각 MCP tool 호출을 stdout 에 한 줄 기록 — journalctl 에서 grep 가능. 형식: TOOL_CALL tool=<name> kw=<arg_keys> (PII 회피 위해 값은 로그 X) """ @_functools_tool.wraps(fn) def _wrapper(*args, **kwargs): try: kw_keys = list(kwargs.keys()) print(f"TOOL_CALL tool={fn.__name__} kw={kw_keys}", flush=True) except Exception: pass - mcp_server/skill_store_mcp.py:60-80 (registration)The FastMCP instance creation and instructions config. The server name is 'skill-store' and the instructions mention upload_skill_draft as a capability for agents without accounts.
mcp = FastMCP( name="skill-store", instructions=( "AI Skill Store는 AI 에이전트용 스킬 마켓플레이스입니다.\n" "▶ 스킬 탐색: search_skills(capability/platform/키워드) → get_skill_schema(스키마 확인) → download_skill(platform 지정)\n" "▶ 스킬 등록 (계정 O): upload_skill(파일+API키) → check_vetting_status / get_vetting_result\n" "▶ 스킬 등록 (계정 X, 에이전트 자립): upload_skill_draft(skill_md+X-Agent-Author) → 응답의 claim_url/agent_identity 을 사용자에게 그대로 전달\n" "▶ Draft 상태 폴링: check_draft_status(claim_token) — skill-level + agent-level claim 상태 동시 확인\n" "▶ Draft 검수 결과 (API 키 없이): get_vetting_result(job_id, claim_token=...) — Draft 업로더 전용 대체 인증\n" "▶ Most Wanted: get_most_wanted — 수요만 있고 공급 없는 스킬 (기회)\n" "▶ 플랫폼/카테고리: list_platforms, list_categories\n" "▶ 설치 가이드: get_install_guide(skill_id, platform)\n" "▶ 상세 조회: get_skill(기본 정보), get_skill_schema(호출 스키마)\n" "\n" "⚠️ Agent Identity (2026-04-23): upload_skill_draft 의 첫 응답에 발급되는 " "agent_secret 은 동일 에이전트 이름의 모든 후속 업로드에서 반드시 동일하게 사용해야 합니다. " "이 값을 저장/재사용하지 않으면 AGENT_SECRET_REQUIRED / AGENT_SECRET_INVALID 오류로 " "업로드가 차단됩니다. 응답의 claim_url / human_action / instruction 은 사용자에게 " "반드시 그대로 표시해야 하며, 에이전트가 대신 claim 을 완료하려 해서는 안 됩니다." ), )