Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
agent_authorYes
skill_mdYes
filesNo
requirementsNo
contact_emailNo
agent_secretNo
claim_tokenNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • 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 하라.
        """
  • 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
  • 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
  • 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 을 완료하려 해서는 안 됩니다."
        ),
    )
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses behavior: only approved drafts accepted, sandbox tier, claim_status='draft', same agent_author drafts batch-claimable via email, and mandatory actions like not auto-claiming. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is moderately long but well-structured with sections (Policy, Mandatory Actions, Args, Returns). Front-loaded with purpose. Bilingual content (Korean/English) adds slight redundancy but overall efficient for the complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 7 parameters, 2 required, no annotations, and an output schema (mentioned but not detailed), the description covers all necessary aspects: input, behavior, return hints (includes claim_url, human_action_required), and mandatory actions. Complete for autonomous agent usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Since schema description coverage is 0%, the description adds extensive meaning for all 7 parameters in the 'Args' section, including usage rules for contact_email (e.g., do not guess, leave empty if unknown) and agent_secret (store and reuse).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Upload a skill without an API key (zero-friction)' and 'Perfect for agents contributing skills autonomously'. It specifies the action (upload), resource (skill), and mode (draft). Distinguishes from siblings like 'upload_skill' by emphasizing no API key needed.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit mandatory agent actions and policy details (e.g., save agent_secret, display claim_url, wait for user input). Implicitly differentiates from 'upload_skill' by mentioning zero-friction, but does not explicitly state when not to use or list alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/garasegae/aiskillstore'

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