Zernote

User research workspace to transcribe interviews and turn conversations into insights.

我该使用它吗

质量与安全性

A
描述质量
99%
模式完整度
93%
命名质量
82%
投毒风险
100%
权限匹配度
100%
协议合规性
100%

发现(1)

  • LOWTool 'get-skills' doesn't follow camelCase/snake_case在 get-skills 中

基于对工具定义和协议合规性的自动分析。

上下文开销

~4,570token 数(工具定义)
~2.0 KB典型响应大小
对注意力有显著影响(占 128k 上下文窗口的 3.57%)

这是每次将服务器的工具加载到模型上下文窗口时所消耗的大致 token 数。数值越高,可用于其他任务的注意力就越少。

安装

一键安装

将以下内容添加到你的 `claude_desktop_config.json` 文件中:

{
  "mcpServers": {
    "zernote": {
      "url": "https://zernote.com/mcp"
    }
  }
}

远程端点

https://zernote.com/mcpstreamable-http

它能做什么

工具清单

工具(17)

🟢 只读🟡 写入🔴 删除⚪ 未知
🟢get-skills(id)

Use for startup ideas, office hours, and “I installed this connector to work through an idea”. Inside: office-hours-zernote (challenge the idea), zernote-plan-research. Omit id for the catalog, then pass id for the full playbook. Not for listing interviews or projects. Works without login.

输入模式

{
  "type": "object",
  "properties": {
    "id": {
      "description": "Skill id. Empty = catalog. Examples: office-hours-zernote, zernote-plan-research.",
      "type": "string"
    }
  }
}
🟢projects_read(action, limit, offset)

Read existing research folders. Not for a new startup idea or office hours — call get-skills first. action=list returns projects for the authenticated user.

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "description": "Read action. Currently only list.",
      "enum": [
        "list"
      ],
      "type": "string"
    },
    "limit": {
      "description": "Max number of projects to return (default 50, max 200).",
      "type": "integer"
    },
    "offset": {
      "description": "Number of projects to skip before returning results (default 0).",
      "type": "integer"
    }
  },
  "required": [
    "action"
  ]
}
🔴projects_edit(action, description, id, name)

Create or rename projects. action=create needs name; action=rename needs id and name. Delete is not supported yet.

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "description": "Edit action.",
      "enum": [
        "create",
        "rename"
      ],
      "type": "string"
    },
    "description": {
      "description": "Optional project description (create/rename).",
      "type": "string"
    },
    "id": {
      "description": "Project UUID (required for rename).",
      "type": "string"
    },
    "name": {
      "description": "Project name (required for create and rename).",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
🟢guides_read(project_id)

Read the interview guide stored on a project. The guide has an intro, ordered blocks (title, minutes, questions, notes), total time, a notes template, and any markdown that did not parse. Returns an empty guide when the project has none.

输入模式

{
  "type": "object",
  "properties": {
    "project_id": {
      "description": "Project UUID.",
      "type": "string"
    }
  },
  "required": [
    "project_id"
  ]
}
🟡guides_create(blocks, intro, notes_template, project_id)

Create the first interview guide on a project. Use this to upload a new guide. Pass structured fields, not markdown: intro, blocks (title, minutes, notes, questions with text/note/key), and notes_template. Needs at least one block with a title. Fails if the project already has a guide — then use guides_edit. Core does not show the guide. Live reads it on the project.

输入模式

{
  "type": "object",
  "properties": {
    "blocks": {
      "description": "Timed sections in interview order.",
      "items": {
        "properties": {
          "minutes": {
            "description": "Minutes allocated to this block.",
            "type": "integer"
          },
          "notes": {
            "description": "Block note shown to the interviewer.",
            "type": "string"
          },
          "questions": {
            "items": {
              "properties": {
                "key": {
                  "description": "True if this is a must-ask question.",
                  "type": "boolean"
                },
                "note": {
                  "description": "Interviewer note under the question.",
                  "type": "string"
                },
                "text": {
                  "description": "Spoken question.",
                  "type": "string"
                }
              },
              "required": [
                "text"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "title": {
            "description": "Block title, e.g. Opening.",
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "type": "object"
      },
      "type": "array"
    },
    "intro": {
      "description": "Spoken opening before the first block.",
      "type": "string"
    },
    "notes_template": {
      "description": "Post-interview notes template (conspect).",
      "type": "string"
    },
    "project_id": {
      "description": "Project UUID.",
      "type": "string"
    }
  },
  "required": [
    "project_id",
    "blocks"
  ]
}
🔴guides_edit(markdown, project_id)

Replace an existing interview guide from markdown. To create the first guide, call guides_create. Text before the first ## is the intro. ## Title (12) or ## Title (7–11) is a block; minutes are the last number in parentheses. Lines starting with - or 1. are questions; a leading * marks a key question; the next indented line is the question note. A paragraph under the block heading before the first question is the block note. ## Конспект is the post-interview notes template, not a block. Optional line `Общее время: 30` or `Total: 30` sets total minutes; otherwise they are the sum of block minutes. Anything that does not match is returned in remainder and kept, not dropped. The previous guide is replaced entirely.

输入模式

{
  "type": "object",
  "properties": {
    "markdown": {
      "description": "Full guide markdown. Replaces the guide stored on the project.",
      "type": "string"
    },
    "project_id": {
      "description": "Project UUID.",
      "type": "string"
    }
  },
  "required": [
    "project_id",
    "markdown"
  ]
}
🟢interviews_read(action, around_chunk_index, folder_id, id, inbox_only, ...)

Read interviews. Typical path: (1) action=list with summary_mode=brief (default) — returns the short # Summary. List items include need_approve_speakers (+ pending_speakers / hint): when true, show interviewer quote links then confirm via assign_speakers; one identity confirm backfills other interviews sharing voice_identity_id. Use summary_mode=brief for renaming or folder triage. Use summary_mode=full when Profile/Tools/Pains/Other topics are needed for deeper analysis. (2) call `search` when you need evidence or counter-evidence for a hypothesis; (3) use action=transcript with around_chunk_index from matching_chunks when a quote window is required. CITATION REQUIRED: when answering the user, actively cite evidence as markdown ["verbatim quote"]({base_url}/project/interview/{id}?t={seconds}) using interview_url / matching chunk url from tool results (includes ?t= when start_seconds is set). Prefer quotes over paraphrase.

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "description": "Read action. Use list (brief or summary_mode=full) for overview; use transcript when you need a quote window.",
      "enum": [
        "list",
        "get",
        "status",
        "transcript"
      ],
      "type": "string"
    },
    "around_chunk_index": {
      "description": "Transcript only: expand a window around this vector-search chunk_index (use instead of blind offset when available).",
      "type": "integer"
    },
    "folder_id": {
      "description": "Deprecated alias for project_id.",
      "type": "string"
    },
    "id": {
      "description": "Interview UUID (required for get, status, transcript).",
      "type": "string"
    },
    "inbox_only": {
      "description": "When true with action=list, list Inbox interviews (no project). Ignored if project_id is set.",
      "type": "boolean"
    },
    "limit": {
      "description": "For list: max interviews (default 20, max 100). For transcript: max characters (default 4000, max 20000).",
      "type": "integer"
    },
    "notes_mode": {
      "description": "List only. none (default) = omit notes body. full = researcher notes (truncated on list). Use summary when notes are not required.",
      "enum": [
        "none",
        "full"
      ],
      "type": "string"
    },
    "offset": {
      "description": "For list: skip count. For transcript without around_chunk_index: character offset (default 0).",
      "type": "integer"
    },
    "project_id": {
      "description": "Optional project UUID to filter list (alias: folder_id).",
      "type": "string"
    },
    "radius_chars": {
      "description": "Transcript only with around_chunk_index: chars before/after chunk match (default 1500, max 8000).",
      "type": "integer"
    },
    "summary_mode": {
      "description": "List only. brief (default) = short # Summary for triage/rename. full = all summary_fragments (profile/tools/pains/other_topics) when brief is insufficient.",
      "enum": [
        "brief",
        "full"
      ],
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
🔴interviews_edit(action, assigned_names, enabled, folder_id, id, ...)

Rename, move, write summary/notes, retranscribe, assign/confirm speakers, or toggle voiceprint. action=rename needs id+title; move needs interview_ids (+ optional project_id); write_summary needs id+summary; write_notes needs id+notes; retranscribe needs id+language (charges transcription tokens again; analyze-only, keeps notes). assign_speakers needs id+speaker_labels+assigned_names. For voiceprint interviewer confirm: one label + real name on one interview after quote links; that identity backfills other interviews sharing voice_identity_id — re-list instead of confirming every card. set_voiceprint needs enabled bool (false purges biometric voice data). If need_approve_speakers=true, show quote links then assign_speakers when the user confirms. If transcript_quality.looks_corrupted=true, offer retranscribe with the correct language. write_notes and write_summary replace the entire notes or summary field (not append).

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "description": "Edit action.",
      "enum": [
        "rename",
        "move",
        "write_summary",
        "write_notes",
        "retranscribe",
        "assign_speakers",
        "set_voiceprint"
      ],
      "type": "string"
    },
    "assigned_names": {
      "description": "Human names matching speaker_labels (assign_speakers). Use a real name, not \"Interviewer N\".",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "enabled": {
      "description": "Voiceprint on/off (set_voiceprint). false deletes stored voice biometrics.",
      "type": "boolean"
    },
    "folder_id": {
      "description": "Deprecated alias for project_id.",
      "type": "string"
    },
    "id": {
      "description": "Interview UUID (rename, write_summary, write_notes, retranscribe, assign_speakers).",
      "type": "string"
    },
    "interview_ids": {
      "description": "Interview UUIDs to move.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "language": {
      "description": "Transcription language code for retranscribe (e.g. en, ru, nl). Charges tokens for audio duration.",
      "type": "string"
    },
    "notes": {
      "description": "Full note text that replaces the existing notes field (write_notes).",
      "type": "string"
    },
    "project_id": {
      "description": "Destination project UUID for move (alias: folder_id). Omit or null for Inbox.",
      "type": "string"
    },
    "speaker_labels": {
      "description": "Diarization labels to assign (assign_speakers), e.g. [\"A\"]. Voiceprint interviewer confirm: one label in this call (same identity then backfills other interviews).",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "summary": {
      "description": "Full summary markdown that replaces the existing summary (write_summary). Expected sections: # Summary, ## Profile, ## Tools & workflow, ## Pains & friction, # Other topics.",
      "type": "string"
    },
    "title": {
      "description": "New title (rename).",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
🟢search(chunks_limit, chunks_offset, interview_id, limit, query)

Semantic search for evidence / counter-evidence after you have interview context (e.g. from list/summary). Optional interview_id scopes search to one interview and returns smaller text windows. Returns matching_chunks[{chunk_index, text, similarity, start_seconds?, url}]. CITATION REQUIRED: actively cite hits to the user as markdown ["verbatim quote from text"]({base_url}/project/interview/{id}?t={seconds}) using each chunk's url field from the tool result (already absolute; includes ?t= when start_seconds is present). Prefer quoting matching_chunks over paraphrase. Expand with interviews_read action=transcript around_chunk_index when a wider quote window is needed.

输入模式

{
  "type": "object",
  "properties": {
    "chunks_limit": {
      "description": "Max matching chunks per interview (default 3 corpus / 8 when interview_id set; max 10 / 20).",
      "type": "integer"
    },
    "chunks_offset": {
      "description": "Skip this many top chunks per interview (default 0).",
      "type": "integer"
    },
    "interview_id": {
      "description": "Optional interview UUID — search only inside this interview; returns smaller chunk windows.",
      "type": "string"
    },
    "limit": {
      "description": "Max number of interviews to return (default 5, max 10). Ignored when interview_id is set.",
      "type": "integer"
    },
    "query": {
      "description": "Natural language search query (hypothesis / claim to evidence).",
      "type": "string"
    }
  },
  "required": [
    "query"
  ]
}
🟢cards_read(codes, folder_id, interview_id, interview_ids, lens, ...)

List L2 coded claim-cards (claim + optional quote + chunk_index + lens + codes + start_seconds? + url). Filter by project_id, interview_id / interview_ids, codes (any match), or lens (workflow|gtm|privacy|other). CITATION REQUIRED: actively cite each card.quote to the user as markdown ["verbatim quote"]({base_url}/project/interview/{id}?t={seconds}) using item.url from the tool result (already absolute; includes ?t= when start_seconds is set). Prefer quotes over paraphrase. Verify with interviews_read action=transcript around_chunk_index when a wider quote window is needed.

输入模式

{
  "type": "object",
  "properties": {
    "codes": {
      "description": "Optional code tags; any match (e.g. pain:synthesis, gtm:pricing).",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "folder_id": {
      "description": "Deprecated alias for project_id.",
      "type": "string"
    },
    "interview_id": {
      "description": "Optional interview UUID to scope cards.",
      "type": "string"
    },
    "interview_ids": {
      "description": "Optional interview UUIDs to scope cards.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "lens": {
      "description": "Optional exact lens filter.",
      "enum": [
        "workflow",
        "gtm",
        "privacy",
        "other"
      ],
      "type": "string"
    },
    "limit": {
      "description": "Max cards to return (default 50, max 200).",
      "type": "integer"
    },
    "offset": {
      "description": "Number of cards to skip (default 0).",
      "type": "integer"
    },
    "project_id": {
      "description": "Optional project UUID — cards from interviews in this project (alias: folder_id).",
      "type": "string"
    }
  }
}
🟢insights_read(action, folder_id, group_id, group_name, id, ...)

Read the project Insights graph in pages or by node. action=list returns a compact folder-wide outline (groups + interviews) plus a page of insight bodies. Filter with interview_id, group_id (DB UUID, not UI group-0), group_name, insight_ids, or ungrouped_only. action=get_node returns one node: insight | interview | group | ungrouped. CITATION REQUIRED: actively cite each item.quote as markdown ["verbatim quote"]({base_url}/project/interview/{id}?t={seconds}) using item.url from the tool result (already absolute; includes ?t= when start_seconds is set). Prefer quotes over paraphrase. Do not use quote_start — that is a character offset, not a player timestamp.

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "description": "list = paged insight bodies + outline. get_node = one insight, interview, group, or ungrouped cluster.",
      "enum": [
        "list",
        "get_node"
      ],
      "type": "string"
    },
    "folder_id": {
      "description": "Deprecated alias for project_id.",
      "type": "string"
    },
    "group_id": {
      "description": "List filter: insight_groups.id UUID (not a render index like group-0).",
      "type": "string"
    },
    "group_name": {
      "description": "List filter: case-insensitive group name match.",
      "type": "string"
    },
    "id": {
      "description": "get_node: insight, interview, or group UUID. Not used for ungrouped.",
      "type": "string"
    },
    "insight_ids": {
      "description": "List filter: only these insight UUIDs.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "interview_id": {
      "description": "List filter: only insights from this interview. Interview must be in the project.",
      "type": "string"
    },
    "limit": {
      "description": "List only: max insight bodies (default 25, max 100).",
      "type": "integer"
    },
    "node_type": {
      "description": "get_node: which node to load.",
      "enum": [
        "insight",
        "interview",
        "group",
        "ungrouped"
      ],
      "type": "string"
    },
    "offset": {
      "description": "List only: skip this many matching insight bodies (default 0).",
      "type": "integer"
    },
    "project_id": {
      "description": "Project/folder UUID (required for list and for get_node group/ungrouped/insight).",
      "type": "string"
    },
    "ungrouped_only": {
      "description": "List filter: only insights that are not in a cross-interview group.",
      "type": "boolean"
    }
  },
  "required": [
    "action"
  ]
}
🟢jtbd_read(action, folder_id, interview_id, project_id, segment_id)

Read JTBD job graph segments and trees. action=list_segments needs folder_id/project_id. action=get_tree needs segment_id or interview_id. CITATION REQUIRED: cite job quotes as ["text"](interview_url).

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "enum": [
        "list_segments",
        "get_tree"
      ],
      "type": "string"
    },
    "folder_id": {
      "description": "Project/folder UUID (list_segments).",
      "type": "string"
    },
    "interview_id": {
      "description": "Interview UUID (get_tree).",
      "type": "string"
    },
    "project_id": {
      "description": "Alias for folder_id.",
      "type": "string"
    },
    "segment_id": {
      "description": "Segment UUID (get_tree).",
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
🔴jtbd_edit(action, category, folder_id, human_edited, i_want_to, ...)

Edit JTBD graph: extract_interview, move_interview, create_segment, fill_in job, update_job, delete_job.

输入模式

{
  "type": "object",
  "properties": {
    "action": {
      "enum": [
        "extract_interview",
        "move_interview",
        "create_segment",
        "fill_in",
        "update_job",
        "delete_job"
      ],
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "folder_id": {
      "type": "string"
    },
    "human_edited": {
      "type": "boolean"
    },
    "i_want_to": {
      "type": "string"
    },
    "interview_id": {
      "type": "string"
    },
    "job_id": {
      "type": "string"
    },
    "language": {
      "description": "Job tree language for extract_interview (en or ru). Defaults to en. Quotes stay in the interview language.",
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "project_id": {
      "type": "string"
    },
    "segment_id": {
      "type": "string"
    },
    "so_that": {
      "type": "string"
    },
    "to_segment_id": {
      "type": "string"
    },
    "when": {
      "type": "string"
    }
  },
  "required": [
    "action"
  ]
}
🔴upload_interview

Opens the in-Claude MCP Apps widget "Upload To Zernote" so the user can drop a file into Inbox in this chat (audio/video max 2GB; text .txt/.md or PDF max 50MB). Use when the user wants to upload/add an interview or get started on an empty account — this is the usual ingest path in Claude. Audio/video starts transcription; text/PDF become ready immediately (no speaker diarization). Claude can name interviews after upload.

输入模式

{
  "type": "object",
  "properties": {}
}
🟡mcp_prepare_upload(content_type, filename, interview_id, size_bytes, title, ...)

App-only: create an Inbox interview and return a presigned R2 upload URL (or multipart upload_id for large files). Pass interview_id (+ upload_id for multipart) to resume without a new card.

输入模式

{
  "type": "object",
  "properties": {
    "content_type": {
      "description": "MIME type for the PUT (optional).",
      "type": "string"
    },
    "filename": {
      "description": "Original filename.",
      "type": "string"
    },
    "interview_id": {
      "description": "Optional: existing empty interview UUID — resume upload without creating a new card.",
      "type": "string"
    },
    "size_bytes": {
      "description": "File size in bytes.",
      "type": "integer"
    },
    "title": {
      "description": "Optional interview title (defaults to sanitized filename).",
      "type": "string"
    },
    "upload_id": {
      "description": "Optional multipart upload id from a previous prepare (resume).",
      "type": "string"
    }
  },
  "required": [
    "filename",
    "size_bytes"
  ]
}
🟡mcp_sign_upload_parts(interview_id, object_key, part_numbers, upload_id)

App-only: return presigned R2 URLs for one or more multipart upload parts (resume-safe).

输入模式

{
  "type": "object",
  "properties": {
    "interview_id": {
      "description": "Interview UUID from prepare.",
      "type": "string"
    },
    "object_key": {
      "description": "R2 object key from prepare.",
      "type": "string"
    },
    "part_numbers": {
      "description": "Part numbers to sign (1-based, max 20).",
      "items": {
        "type": "integer"
      },
      "type": "array"
    },
    "upload_id": {
      "description": "Multipart upload id from prepare.",
      "type": "string"
    }
  },
  "required": [
    "interview_id",
    "object_key",
    "upload_id",
    "part_numbers"
  ]
}
🟡mcp_confirm_upload(filename, interview_id, language, multipart_parts, multipart_upload_id, ...)

App-only: confirm a completed R2 upload (single PUT or multipart) for an Inbox interview. Audio/video is enqueued for transcription (STT + diarization). Text (.txt/.md) and PDF are stored and marked ready immediately — no transcription.

输入模式

{
  "type": "object",
  "properties": {
    "filename": {
      "description": "Original filename.",
      "type": "string"
    },
    "interview_id": {
      "description": "Interview UUID from prepare.",
      "type": "string"
    },
    "language": {
      "description": "STT language for audio/video (e.g. en, ru, nl). Not used for text/PDF (no transcription).",
      "type": "string"
    },
    "multipart_parts": {
      "description": "Completed parts (part_number + etag) when using multipart.",
      "items": {
        "properties": {
          "etag": {
            "type": "string"
          },
          "part_number": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "type": "array"
    },
    "multipart_upload_id": {
      "description": "Multipart upload id — complete assembly before confirm.",
      "type": "string"
    },
    "object_key": {
      "description": "R2 object key from prepare.",
      "type": "string"
    },
    "size_bytes": {
      "description": "File size in bytes.",
      "type": "integer"
    },
    "title": {
      "description": "Optional title to apply before confirm.",
      "type": "string"
    }
  },
  "required": [
    "interview_id",
    "object_key",
    "filename",
    "size_bytes",
    "language"
  ]
}

社区

评价此服务器

证据

最近观测

已验证未记录版本17 个工具
已验证未记录版本14 个工具
需要认证未记录版本—