A side-by-side look at the Fmeca and Cv MCP Server MCP servers: transport, auth, tools, quality signals, and the config to install either one in Claude, Cursor, Windsurf or VS Code.
Verdict: choose Fmeca for communication work as a local process; choose Cv MCP Server for communication as a local process. You can also run both side by side in one mcpServers block.
session.open, append, state.get.CARBON_VOICE_API_KEY, LOG_LEVEL, LOG_DIR.list_messages, get_message, get_recent_messages.Rows where the two servers differ are marked.
| Spec | F Fmeca | |
|---|---|---|
| Category | Communication | Communication |
| Quality signal | 56/100 · Good | 64/100 · Good |
| Install command | Confirmed | Confirmed |
| Transport | Local subprocess (stdio) | Local subprocess (stdio) |
| Auth | No auth required | other |
| Pricing | Free | BYOK (pay the provider) |
| Required env vars | None listed | CARBON_VOICE_API_KEYLOG_LEVELLOG_DIR |
| Verified owner | Community listing | Community listing |
| GitHub stars | 0 | 10 |
| Directory activity | 0 views 0 installs 0 upvotes | 4 views 0 installs 0 upvotes |
| Tools | 12 | 42 |
| Full listing | View Fmeca | View Cv MCP Server |
session.openStart or resume a session; optionally selects the matrix strategy and records analysis metadata (`title`, `level`, `aspect`, `scope_paths`, `parent`), all fixed for the session's life.appendAdd a failure mode (`add_failure_mode`), add a mitigation (`add_mitigation`), or re-score an existing failure mode (`rescore`). `set_mitigation_status` moves a mitigation through planned, implemented, verified or abandoned (verified needs evidence other than a note). `set_failure_mode_disposition`…state.getRead-only projection of the session: every failure mode with its computed criticality, residual, and standing.risk.nextThe highest-criticality unmitigated failure mode — what to address next.readiness.assessThe readiness gate: is every High/Medium residual reduced and every blocker cleared?report.exportThe full FMECA table for the session.scoring.catalogThe fixed observation vocabulary for a strategy — the ids the caller draws from.analyzeStateless one-shot: hand over an entire analysis, get back the entire computed report. No session, no persistence.session.listEvery analysis in the state dir (the project's `.fmeca/` when there is one), with its metadata, standing counts (excluded failure modes are counted separately), `ready`, `ready_verified` and resolution counts. Filters: `level`, `aspect`, `parent`. A log that fails to replay is listed with its error.vetAdvisory: asks Jev (via OpenRouter, needs `OPENROUTER_API_KEY`) whether observations, mitigations, coverage and readiness hold up, and returns probabilities beside the deterministic gate. Never writes or changes a score; without a key it reports `vet_unavailable`.actions.suggestRead-only. Reads a session (or an inline analysis) under a [risk policy](docs/policies.md) and suggests actions: per-failure-mode risk bands, evidence confidence, dispositions, tagged actions and a sign-off verdict. Never changes the analysis or the log.policy.listRead-only. The built-in policy presets and the project's `.fmeca/policies/` files, each with its name, hash and class. A project file that does not load is listed with its `error`.list_messagesList messages with filtering by date, conversation, folder, workspace, creator or language.
USE WHEN: The general-purpose message reader; full bodies incl. transcript and AI summary. Max date span 183 days. `user_ids` filters by SENDER — for messages exchanged WITH someone, pass `conversation_id` from `list_conversations`. Use presigned URLs as-is.
USE INSTEAD: `get_recent_messages` for a quick look at the latest few (hard-capped at 10, no paging). `search_message_ids` for filters this cannot express — notified state, mentions or labels.
FIRST: `workspace_id` comes from `get_workspaces_basic_info` (field `id`) — call it first if you don't have one.
EXAMPLE: {"workspace_id":"ws-abc","start_date":"2026-09-01T00:00:00Z","size":25}
RETURNS: `{page, size, sort_direction, total, results_count, has_next_page, filters, results: [{id, transcript?, ai_summary?, audio_url?, creator_id, conversation_id?, duration_ms, reply_count, status, type, created_at, ...}]}`. Page on `has_next_page`/`total` — do not guess.
NARROW: pass response_fields ["total","has_next_page","results.id","results.transcript","results.created_at"] unless you need more — the full payload is much larger.get_messageGet one message by ID, optionally expanded with related records.
USE WHEN: You have a message ID. `fields` ADDS related data (`conversation`, `creator`, `labels`) — it does not narrow the response. Use `response_fields` to narrow.
USE INSTEAD: `list_messages` when you do not have an ID, or need several messages — it already returns full bodies, so fetching each one again is wasted.
FIRST: `id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one.
EXAMPLE: {"id":"msg-abc","fields":"creator"}
RETURNS: `{message: {id, transcript?, ai_summary?, audio_url?, creator_id, duration_ms, status, type, attachments?, created_at, ...}}`.get_recent_messagesGet up to 10 of the most recent messages, each with its conversation, creator and labels.
USE WHEN: A quick "what just happened" glance. Pre-joined, so no follow-up calls for creator or conversation names.
USE INSTEAD: `list_messages` whenever you need more than 10, any date range, paging, or a filter other than conversation and language — this tool supports none of those.
EXAMPLE: {"conversation_id":"conv-abc"}
RETURNS: `{results: [{message: {...}, conversation: {...}, creator: {...}, labels: [...]}]}`. No total and no paging: the cap of 10 is the whole answer.create_conversation_messagePost a message into an existing conversation, or reply in a thread.
USE WHEN: You have a `conversation_id`. Pass `parent_id` (a message ID) to reply as a thread. Either `transcript` or `links` is required — the schema marks neither individually, so both param descriptions say so.
USE INSTEAD: `create_direct_message` to reach people who are not already in a conversation. `create_voicememo_message` for a standalone memo.
FIRST: `id` comes from `list_conversations` (field `results[].id`) — call it first if you don't have one.
EXAMPLE: {"id":"conv-abc","transcript":"Agreed, shipping Friday."}
RETURNS: `{message: {id, link, transcript?, status, type, conversation_id, created_at, ...}}`.
ERROR BAD_REQUEST: Neither `transcript` nor `links` was provided. — Pass at least one of them.create_direct_messageSend a direct message to one or more people, by user ID or email.
USE WHEN: Reaching people outside an existing conversation. Address it with `to.user_ids` or `to.emails`. Requires `transcript` or `links`.
USE INSTEAD: `create_conversation_message` when a conversation already exists — a DM starts a separate thread rather than joining it.
FIRST: `to.user_ids` comes from `search_users` (field `id`) — call it first if you don't have one.
EXAMPLE: {"to":{"user_ids":["user-abc"]},"transcript":"Quick question about the deck."}
RETURNS: `{message: {id, link, transcript?, status, conversation_id, created_at, ...}}`.
ERROR BAD_REQUEST: A user ID is invalid, or neither `transcript` nor `links` was provided. — Resolve people with `search_users` — never pass a display name as a user ID — and include a transcript or links.create_voicememo_messageCreate a voice memo, either from text (spoken via text-to-speech) or from an audio file at a URL.
USE WHEN: Pass `transcript` (2-5000 chars) to have Carbon Voice speak the text, or an https `audio_url` to upload audio, which wins over `transcript`. File it with `folder_id`, whose type must match, or a `workspace_id`.
USE INSTEAD: `create_conversation_message` to post into an existing conversation, or `create_direct_message` to send to specific people. A voice memo is standalone and lives in a folder or workspace.
EXAMPLE: {"transcript":"Reminder to review the pricing deck before Friday."}
RETURNS: `{message: {id, link, transcript?, audio_url?, duration_ms, status, type, created_at, ...}}`. `status` is often `processing` at first.
ERROR INVALID_AUDIO_URL: `audio_url` is not https, unreachable, too large, timed out, embeds credentials, or resolves to a private address. — The message gives the reason. Use a public https URL, or pass `transcript` instead.
ERROR BAD_REQUEST: None of `transcript`, `audio_url` or `links` was provided, or the transcript is outside 2-5000 characters. — Provide one of the three, and keep the transcript within the length limits.add_attachments_to_messageAttach one or more link URLs to an existing message.
USE WHEN: Adding external links to a message that already exists.
USE INSTEAD: `create_message_share_link` to share a Carbon Voice message outward — that produces a link, this consumes them.
FIRST: `id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one.
EXAMPLE: {"id":"msg-abc","links":["https://example.com/spec"]}
RETURNS: `{...}` confirmation with the resulting attachments.get_userGet a user's full profile by ID — names, languages, voice settings, workspace roles.
USE WHEN: You already have a user ID and need complete details.
USE INSTEAD: `search_user` / `search_users` to FIND someone by email, phone or name. `get_current_user` for the caller — this tool needs an explicit ID and will not default to you.
FIRST: `id` comes from `search_users` (field `id`) — call it first if you don't have one.
EXAMPLE: {"id":"user-abc"}
RETURNS: `{id, first_name, last_name?, languages, voice_gender, workspace_ids, workspace_roles, user_type, created_at, ...}`.
NARROW: pass response_fields ["id","first_name","last_name","workspace_ids"] unless you need more — the full payload is much larger.search_userFind a single user by email, phone or name.
USE WHEN: Resolving ONE person. Supply exactly one of `email`, `phone` or `name`. Name search only matches your own contacts.
USE INSTEAD: `search_users` for several people in one call — it takes arrays and saves a round trip per person. `get_user` when you already have the ID.
EXAMPLE: {"email":"someone@example.com"}
RETURNS: `{id, full_name, first_name, last_name?, link, image_url?, languages?, ...}`. Use `id` wherever another tool asks for a user ID.
ERROR NOT_FOUND: Nobody matched, or a name search hit a non-contact. — Try an email or phone instead of a name; name search is limited to your contacts.search_usersResolve several users at once by emails, phones, IDs or names.
USE WHEN: Turning a list of people into user IDs in one call — the right first step before any tool that takes user IDs. Name search only matches your contacts.
USE INSTEAD: `search_user` for a single lookup. `get_user` for a full profile once you have the ID.
EXAMPLE: {"names":["Brett"]}
RETURNS: Array of `{id, full_name, first_name, last_name?, link, languages?, ...}`. If a name returns more than one candidate, ask which person was meant rather than guessing.
NARROW: pass response_fields ["id","full_name"] unless you need more — the full payload is much larger.get_current_userGet the calling user's own identity, workspaces and settings.
USE WHEN: Establishing who you are acting as, or finding the caller’s workspace IDs before a workspace-scoped call. Takes no arguments.
USE INSTEAD: `get_user` for somebody else (it requires an explicit ID). `get_workspaces_basic_info` if you only need workspace IDs and names — it is far smaller than this response.
EXAMPLE: {}
RETURNS: `{success, user: {user_guid, first_name, last_name?, email_txt?, phone_txt?, workspace_guids, identities, entries, environments, lifecycle_events, notification_settings, settings, ...}, settings: {...}}`. This payload is LARGE — several unbounded arrays and an open settings map.
NARROW: pass response_fields ["user.user_guid","user.first_name","user.email_txt","user.workspace_guids"] unless you need more — the full payload is much larger.list_conversationsList your conversations from the last 6 months, optionally filtered by participants, type and name.
USE WHEN: Finding a `conversation_id`. Filter with `user_ids` plus `match`, `types` and `name` — YOUR DM with someone is `user_ids: ["<their id>"], types: ["directMessage"]`. You are always an implicit participant, so never pass your own ID. Filters AND together; if several match, ask which was meant.
USE INSTEAD: `get_conversation` when you already have an ID and want full detail — this returns only id, name, workspace_id and type.
EXAMPLE: {"user_ids":["user-abc"],"types":["directMessage"]}
RETURNS: `{results_count, results: [{id, name, workspace_id, type}]}` where type is `directMessage` | `customerConversation` | `namedConversation` | `asyncMeeting`. No paging: `results_count` is what is returned, after filtering. `name` adds `unfiltered_count`: rows the name was matched against, AFTER `user_ids`/`types`. ALWAYS check it before saying no such conversation exists. 0 of 47 means none matched that string (misspelt, or >6 months old); 0 of 0 means your other filters left nothing — not that the caller has none. Widen the filters or ask; never report it does not exist.Paste either block (or both) into your client config, e.g. claude_desktop_config.json or ~/.cursor/mcp.json. We only show a config when the install command is confirmed.
{
"mcpServers": {
"fmeca": {
"command": "npx",
"args": [
"-y",
"@matthew-cochran/fmeca"
]
}
}
}
{
"mcpServers": {
"phononx-cv-mcp-server": {
"command": "npx",
"args": [
"-y",
"@carbonvoice/cv-mcp-server"
],
"env": {
"CARBON_VOICE_API_KEY": "YOUR_CARBON_VOICE_API_KEY_HERE",
"LOG_LEVEL": "YOUR_LOG_LEVEL_HERE",
"LOG_DIR": "YOUR_LOG_DIR_HERE"
}
}
}
}