Atlas / MCP servers / amarthaid / workbench

workbenchBLOCK

mcp/amarthaid/workbench

One MCP endpoint. Every tool your agent needs. Self-hosted, per-user OAuth. 20+ SaaS integrations and invite you to add more.

Verdict
BLOCK
Grade
F
Trust score
34 /100
Exposed tools
200 140r · 87w · 9d
Transport
streamable-http
License
MIT
Stars
6
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

Self-hosted MCP tool aggregator. One endpoint, per-user OAuth, 194 tools across 16 integrations — behind 9 meta-tools.

**Website** · Documentation · Quickstart · Integrations · Build a plugin · Deploy

[](https://github.com/barockok/workbench/actions) [](https://codecov.io/gh/barockok/workbench) [](LICENSE)

Give an agent one MCP endpoint instead of sixteen. workbench holds a separate OAuth connection per user per provider, encrypts every token at rest, and exposes every integration through a fixed set of nine meta-tools — so the agent's tool list stays the same size whether one integration is connected or all of them.

flowchart LR
Agent["Agent"] -->|JSON-RPC| MCP["POST /mcp"]
Script["Script / cron"] -->|plain JSON| REST["POST /rest/:integration"]
MCP --> Meta["9 meta-tools"]
Meta --> Reg["Plugin registry16 integrations · 194 tools"]
REST --> Reg
Reg -->|credential injected| APIs["Jira · GitHub · Slack · Google · ..."]
Portal["Portal"] --> Store[("Encrypted tokens")]
Meta -.-> Store

Anything that is not an agent can skip MCP entirely: POST /rest/:integration runs the same tools over plain JSON, on the same credentials, with no JSON-RPC framing and no 60,000-character result cap.

Quickstart

Read from source at commit 7477d052587dOBSERVED · 2026-10-13
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add site --env CAPTURE_PROXY_PASSWORD=${CAPTURE_PROXY_PASSWORD} --env GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} --env GITLAB_CLIENT_SECRET=${GITLAB_CLIENT_SECRET} --env GOOGLE_GMAIL_CLIENT_SECRET=${GOOGLE_GMAIL_CLIENT_SECRET} -- npx -y @a-workbench/[email protected]
claude-desktop
{
  "mcpServers": {
    "site": {
      "command": "npx",
      "args": [
        "-y",
        "@a-workbench/[email protected]"
      ],
      "env": {
        "CAPTURE_PROXY_PASSWORD": "${CAPTURE_PROXY_PASSWORD}",
        "GITHUB_CLIENT_SECRET": "${GITHUB_CLIENT_SECRET}",
        "GITLAB_CLIENT_SECRET": "${GITLAB_CLIENT_SECRET}",
        "GOOGLE_GMAIL_CLIENT_SECRET": "${GOOGLE_GMAIL_CLIENT_SECRET}"
      }
    }
  }
}
03

Exposed tools (200)

140 read · 87 write · 9 destructive. Blast radius: 9 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.

ToolRiskDescription
acme_cfg_searchreadx
acme_createwriteCreate an issue
acme_listreadlist
acme_off_searchreadSearch acme records
acme_searchreadSearch issues
alpha_toolreadAlpha
asana_add_commentwriteAdd a comment (story) to an Asana task. Plain text only. Returns { gid, created_at } of the new comment. Use this for status updates or context on an existing task — to change task fields themselves use asana_update_task instead.
asana_create_taskwriteCreate a new Asana task in a project. Returns the created task including its gid — keep the gid to update, complete (asana_update_task with completed:true), or comment on it later. dueOn is YYYY-MM-DD; assignee is a user gid (find one with asana_search_users).
asana_list_projectswriteList Asana projects as slim rows: { gid, name } (default 10, set limit for more). Scope to one workspace via workspaceId, otherwise all workspaces you can see. Use the gid with asana_list_tasks / asana_create_task.
asana_list_tasksreadList tasks in an Asana project as slim rows: { gid, name, completed, due_on, assignee } (default 10, set limit for more). Use asana_get_task on a gid when you need notes or the permalink. Find project gids with asana_list_projects.
asana_list_teamswriteList teams in an Asana organization as slim rows: { gid, name } (default 10, set limit for more). organizationId is the workspace gid of an organization.
asana_search_usersreadSearch Asana users in a workspace as slim rows: { gid, name, email } (default 10, set limit for more). Omit query to list everyone. Use the gid as assignee / assigneeGid in asana_create_task and asana_update_task.
beta_toolreadBeta
bitbucket_add_pr_commentwriteAdd a comment to a pull request. Plain
bitbucket_approve_prreadApprove a pull request as the connected user (the green checkmark reviewers see). Non-destructive and revocable in the Bitbucket UI. Review the changes with bitbucket_get_pr_diff before approving. Returns { approved: true }.
bitbucket_create_prwriteCreate a Bitbucket pull request from sourceBranch into destinationBranch (default \
bitbucket_decline_prreadDecline (reject) a pull request. WARNING: this closes the PR without merging — the author must open a new PR to resurrect it. Prefer bitbucket_request_changes if you only want revisions. Returns the PR
bitbucket_get_filereadRead a file
bitbucket_get_pipelinereadGet a single Bitbucket pipeline by uuid (or build number) as a slim object { uuid, build_number, state, result, ref_name, created_on, completed_on }. Use to poll a pipeline you triggered (state: PENDING | IN_PROGRESS | COMPLETED; result: SUCCESSFUL | FAILED | STOPPED). workspace defaults to the user
bitbucket_get_prreadGet one pull request: title, description, state, author, source/destination branches, reviewers with approval status, comment/task counts, timestamps, and html URL. Always check this (state, approvals, open tasks) before bitbucket_merge_pr. For the code changes use bitbucket_get_pr_diff.
bitbucket_get_reporeadGet one Bitbucket repository: slug, name, full_name, privacy, main branch, last update, description, and the https clone URL. Use after bitbucket_list_repos when you need the clone URL or to confirm the main branch before creating a PR.
bitbucket_list_default_reviewersreadList the default reviewers that will auto-attach to new pull requests on a repository. Returns slim rows { display_name, uuid, reviewer_type:
bitbucket_list_pr_commentsreadList comments on a pull request as slim rows: { id, author, text, inline: { path, line } for code comments (absent for general comments), created_on }. pagelen defaults to 20. Returns { comments, hasMore }. Use before replying with bitbucket_add_pr_comment to see existing review feedback.
bitbucket_list_pr_commitsreadList the commits in a pull request as slim rows: { hash (12 chars), message (first line only), author, date }. Cheaper than the diff for understanding the shape/history of a PR. Returns { commits, hasMore }.
bitbucket_list_prsreadList pull requests in a repository as slim rows (id, title, state, author, source/destination branch, updated_on).
bitbucket_list_reposreadList repositories in a Bitbucket workspace as slim rows (slug, name, full_name, is_private, main branch, updated_on, description) — no links/avatars/clone noise.
bitbucket_merge_prwriteMerge a pull request into its destination branch. IRREVERSIBLE: this lands the commits on the destination branch — check state, approvals, and open tasks with bitbucket_get_pr first, and only merge when explicitly asked to. Optional:
bitbucket_request_changesreadMark a pull request as \
bitbucket_search_usersreadSearch Bitbucket workspace members by name or username to resolve user UUIDs for use with bitbucket_create_pr reviewers. Substring match against display_name OR nickname (case-insensitive) —
bitbucket_stop_pipelinewriteStop a running Bitbucket pipeline by uuid. Returns { ok: true } on success (204 No Content); on failure returns Bitbucket
bitbucket_trigger_pipelinewriteTrigger a Bitbucket Pipelines run on a branch (
browser_audio_startwriteGive this tab a live audio pipe so you can take part in a browser call (Zoom web, Slack huddle, Meet, Jitsi).
browser_audio_stopwriteEnd the audio pipe on this tab: the SSE stream gets
browser_await_downloadreadWait for the download armed by browser_expect_download to finish, and return { name, bytes, expiresAt } for the file now in your workspace. Read it with files_read, hand it on with files_presign, or upload it into another page with browser_upload_file.
browser_clickreadClick at viewport coordinates (x, y) in this tab.
browser_closereadClose this tab. The browser and its logged-in profile stay; other tabs are untouched. An idle browser closes itself after BROWSER_SESSION_TTL_SECONDS.
browser_evaluatewriteRun JavaScript in the page and return its value — the page.evaluate of this toolset. Use it for anything coordinates cannot express: click by selector (document.querySelector(
browser_keyreadPress a key or chord, e.g.
browser_live_urlreadGet a short-lived URL to watch and take over your browser in a web canvas. Open it to drive the same browser by hand, then return control to the model.
browser_navigatereadNavigate this tab to a URL. Returns the final url and page title.
browser_read_textreadRead the visible text of the current page (document.innerText) as plain text — far cheaper than a screenshot for text-heavy pages, forms, and reading. Use this instead of browser_screenshot when you don
browser_screenshotreadShot
browser_scrollreadScroll the viewport up/down/left/right by an optional pixel amount (default 600).
browser_startwriteOpen a new tab in your browser and return its session_id. Pass it to every other browser_* call. Call it once per independent task; two agents each get their own tab and never step on each other. All tabs share one browser, so a login in one is visible in the others.
browser_tabsreadList the tabs open in your browser: session_id, url, title, and whether this toolset can drive it (a popup the page opened is listed but not driveable).
browser_typereadType text into the currently focused element. Click the field first.
browser_upload_filewritePut a file from your workspace into a file input on the current page.
clevertap_get_campaign_reportreadGet delivery and engagement report for a campaign by numeric ID.
clevertap_get_campaignsreadList campaigns within date range (id, name, scheduled_on, status). ${YYYYMMDD_DESC}
clevertap_get_daureadGet Daily Active Users trend (unique App Launched) for date range. ${YYYYMMDD_DESC}
clevertap_get_event_countreadCount users who performed an event within date range. ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${CQL_INFO} Supports property filters (event_properties, session_properties, common_profile_properties, advanced_query). Auto-polls if async.
clevertap_get_event_trendreadGet daily/weekly/monthly trend for an event. Supports unique count and sum of numeric property. ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${CQL_INFO} Auto-polls if async.
clevertap_get_eventsreadQuery event data for an event within a date range. ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${CQL_INFO} Returns cursor for paginated results — use clevertap_get_events_cursor for next pages.
clevertap_get_events_cursorreadFetch next page of event results using cursor from clevertap_get_events.
clevertap_get_message_reportreadGet delivery/engagement report (sent, delivered, opened, clicked) filtered by channel, delivery type, status, label. ${YYYYMMDD_DESC}
clevertap_get_profilereadRetrieve a user profile by identity, email, or objectId (GUID). Provide at least one. ${PREDEFINED_PROFILE_INFO}
clevertap_get_profile_countreadCount profiles who performed an event within date range. ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${CQL_INFO} Supports property filters (event_properties, session_properties, common_profile_properties, advanced_query). Auto-polls if async.
clevertap_get_profiles_by_eventreadGet profiles of users who performed an event within date range. ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${PREDEFINED_PROFILE_INFO} ${CQL_INFO} Returns cursor — use clevertap_get_profiles_cursor for next pages.
clevertap_get_profiles_cursorreadFetch next page of profiles using cursor from clevertap_get_profiles_by_event.
clevertap_get_real_time_countsreadGet count of users active right now (last 5 minutes). Optionally breakdown by user type.
clevertap_get_top_property_countreadGet top property value counts for an event (e.g. top product categories). ${YYYYMMDD_DESC} ${SYSTEM_EVENTS_INFO} ${CQL_INFO} Auto-polls if async.
clevertap_get_uninstall_reportdestructiveGet uninstall count trend for date range. ${YYYYMMDD_DESC}
clevertap_list_projectsreadList all configured CleverTap projects (name, account ID, region).
clevertap_pollreadPoll async CleverTap result via req_id until success/fail.
clevertap_set_projectswriteSet all CleverTap projects at once via JSON. Overwrites existing. Use for multi-account bulk config. Input is JSON array: [{\
confluence_create_pagewriteCreate a Confluence page in a space. body is Confluence storage format (XHTML) — plain text works for simple pages. Pass parentId to nest under an existing page. spaceKey is resolved to the numeric spaceId internally; pass spaceId directly to skip the lookup. Returns the new page
confluence_delete_pagedestructiveDelete a Confluence page by ID (moves it to space trash). Irreversible via this API.
confluence_get_pagereadGet a Confluence page by ID, including its body (storage XHTML), version number, and space. Returns { id, title, spaceKey, version, body, url, attachments }.
confluence_list_spacesreadList Confluence spaces (up to
confluence_search_pagesreadSearch Confluence pages. Pass
confluence_update_pagewriteUpdate a Confluence page
delete_jotdestructiveDelete a jot you own by name. Returns FORBIDDEN if another user owns it, NOT_FOUND if it doesn
deploy_jotwriteBegin deploying a static web artifact to /j/<name>/. Returns an upload URL and a single-use token (valid ~5 min). Package your site directory as a gzip tarball and upload it, e.g.:
exec_toolwriteExec
execute_toolswriteExecute one or more tools in a single call. Runs them concurrently (bounded) and returns a
files_deletedestructiveDelete a file from your workspace. Files expire on their own after 24 hours; this is for reclaiming quota early.
files_listreadList the files in your workspace — the per-user scratch area that browser downloads land in and that uploads are read from. Returns name, bytes, mtime and expiresAt for each. ${RETENTION}
files_presignreadMint a short-lived URL for one workspace file, so something that cannot hold a workbench credential can fetch or write it — a service that ingests by URL, an upload target, or a transfer that should not pass through your context. op
files_readreadRead a workspace file. Use encoding
files_statreadGet one workspace file
files_writewriteWrite a file into your workspace, so it can be uploaded into a page with browser_upload_file or handed to another integration. Use encoding
get_tool_schemareadGet input schema for a specific tool
github_add_issue_commentwriteAdd a comment to a GitHub issue (also works as a PR discussion comment). Returns { id, html_url }. For inline code-review comments on a PR diff, use github_add_pr_comment with type:
github_add_pr_commentwriteComment on a pull request. type:
github_cancel_workflow_runwriteCancel an in-progress GitHub Actions workflow run by id. Returns { ok: true } on success; on failure returns GitHub
github_create_issuewriteCreate a GitHub issue with optional body and labels. Returns the created issue. Follow up with github_update_issue / github_add_issue_comment; use github_list_issues to find existing ones first and avoid duplicates.
github_create_or_update_filewriteCreate or update a single file in a GitHub repo with a commit message (content is plain text, base64-encoded automatically). Updating an existing file REQUIRES its current sha — fetch it with github_get_content first or the call fails with 409/422. Defaults: branch=main.
github_create_prwriteOpen a GitHub pull request from head branch into base branch. Returns the created PR. Follow up with github_get_pr / github_get_pr_diff to review it and github_merge_pr to land it.
github_create_pr_reviewwriteSubmit a formal review verdict on a pull request: event APPROVE, REQUEST_CHANGES, or COMMENT, with an optional summary body. Returns { id, state }. For a single inline or discussion comment without a verdict, use github_add_pr_comment instead.
github_get_contentreadRead a file (base64 content + sha) or list a directory in a GitHub repo at an optional ref. Returns the file
github_get_issuereadGet one GitHub issue: { number, title, body (truncated to 2000 chars), state, author, labels, assignees, comments, html_url }. Use github_list_pr_comments-style follow-ups via github_add_issue_comment to reply.
github_get_prwriteGet one pull request: title, body (truncated to 2000 chars), state, merged/mergeable, author, head/base refs, draft, additions/deletions/changed_files, comment counts, html_url. Use github_get_pr_diff for the actual code changes.
github_get_pr_diffreadRead a pull request
github_get_reporeadGet one GitHub repository
github_get_workflow_runwriteGet a single GitHub Actions workflow run by id as a slim object { id, name, head_branch, event, status, conclusion, run_started_at, html_url }. Use this to poll a run you triggered with github_trigger_workflow (status: queued | in_progress | completed; conclusion: success | failure | cancelled | ...
github_list_branchesreadList branches in a GitHub repository as slim rows { name, protected }. Defaults: 30 per page. Use github_list_commits to see history on a branch.
github_list_commitsreadList commits in a GitHub repository as slim rows { sha (12 chars), message (first line), author, date }, newest first. Pass sha to start from a branch/commit. Defaults: 30 per page.
github_list_issuesreadList issues in a GitHub repository as slim rows { number, title, state, author, labels, assignee, comments, updated_at, is_pr }. Note: GitHub
github_list_pr_commentsreadList comments on a pull request as slim rows { id, author, body, path?, line?, created_at }. type:
github_list_prsreadList pull requests in a GitHub repository as slim rows { number, title, state, author, head, base, draft, updated_at }. Defaults: state=open, 10 per page. Use github_get_pr for one PR
github_list_releasesreadList releases for a GitHub repository as slim rows { tag_name, name, draft, prerelease, published_at }, newest first. Defaults: 30 per page.
github_list_reposreadList the authenticated user
github_list_workflow_runsreadList GitHub Actions workflow runs (CI status) as slim rows { id, name, head_branch, event, status, conclusion, run_started_at, html_url }, newest first. Optional branch and status (e.g. completed, in_progress, failure) filters. Default 10 per page.
github_merge_prwriteMerge a pull request into its base branch. IRREVERSIBLE — this lands the code; confirm the right PR number first (github_get_pr) and prefer reviewing via github_get_pr_diff before merging. Optional merge_method (merge|squash|rebase) and commit_title.
github_rerun_workflow_runwriteRe-run a GitHub Actions workflow run by id (all jobs). Pass failedOnly to re-run only the failed jobs (rerun-failed-jobs). Returns { ok: true } on success; on failure returns GitHub
github_search_codereadSearch code across GitHub as slim rows { path, repository, html_url }. Pass full search qualifiers in q (e.g.
github_search_issuesreadSearch issues and PRs across GitHub as slim rows { number, title, state, author, labels, comments, updated_at, is_pr, repository }. Pass full qualifiers in q (e.g.
github_trigger_workflowwriteTrigger a GitHub Actions workflow run via workflow_dispatch.
github_update_issuewriteUpdate a GitHub issue — set any of title, body, state (open|closed), labels, assignees; only the fields you pass are changed. Closing an issue = state:
gitlab_add_issue_commentwriteAdd a comment (note) to a GitLab issue. Returns { id, created_at }. Use gitlab_get_issue to read the issue first.
gitlab_add_mr_commentwriteComment on a merge request. Plain
gitlab_approve_mrwriteApprove a merge request as the connected user. Non-destructive and revocable. Review the changes with gitlab_get_mr_diff first. Returns { approved: true, approvals_left }. (Approvals are a GitLab Premium feature on some instances; on those this 404s.)
gitlab_cancel_pipelinereadCancel a running GitLab pipeline by id (cancels its running/pending jobs). Returns the slim pipeline; on failure returns GitLab
gitlab_close_mrwriteClose (reject) a merge request without merging. The author must reopen it to resurrect it. Prefer leaving review feedback via gitlab_add_mr_comment if you only want revisions. Returns { iid, state }.
gitlab_create_issuewriteCreate a GitLab issue with optional description and labels. Returns the created issue (note its
gitlab_create_mrwriteOpen a GitLab merge request from sourceBranch into targetBranch (default
gitlab_create_or_update_filewriteCreate or update a single file in a GitLab project with a commit message (content is plain text). Auto-detects create vs update by checking whether the file already exists on
gitlab_get_clone_urlreadMint a temporary authenticated HTTPS git URL for a project (https://oauth2:<token>@host/path.git), usable for clone/pull/push (the
gitlab_get_filereadRead a file
gitlab_get_issuereadGet one GitLab issue: { iid, title, description (truncated to 2000 chars), state, author, labels, assignees, user_notes_count, web_url }. Reply with gitlab_add_issue_comment.
gitlab_get_mrwriteGet one merge request: iid, title, description (truncated to 2000 chars), state, merge_status, author, source/target branches, draft, upvotes/downvotes, user_notes_count, web_url. Check this (state, merge_status, approvals) before gitlab_merge_mr. For the code changes use gitlab_get_mr_diff.
gitlab_get_mr_diffwriteRead a merge request
gitlab_get_pipelinereadGet a single GitLab CI pipeline by id as a slim object { id, status, ref, sha, source, web_url, created_at, updated_at }. Use to poll a pipeline you triggered (status: created | pending | running | success | failed | canceled).
gitlab_get_projectreadGet one GitLab project
gitlab_list_brancheswriteList branches in a GitLab project as slim rows { name, default, protected, merged }. Use gitlab_list_commits to see history on a branch. Defaults: 20 per page.
gitlab_list_commitsreadList commits in a GitLab project as slim rows { id (12 chars), title, author, date }, newest first. Pass
gitlab_list_issuesreadList issues in a GitLab project as slim rows { iid, title, state, author, labels, assignees, user_notes_count, updated_at }. Defaults: state=opened, 20 per page. Use gitlab_get_issue for one issue
gitlab_list_mr_commentswriteList comments (notes) on a merge request as slim rows { id, author, body, system, resolvable, created_at }. system notes are GitLab
gitlab_list_mr_commitswriteList the commits in a merge request as slim rows { id (12 chars), title, author, date }. Cheaper than the diff for understanding an MR
gitlab_list_mrswriteList merge requests in a GitLab project as slim rows { iid, title, state, author, source_branch, target_branch, draft, updated_at }. state defaults to opened; pass merged/closed/all for history. Use gitlab_get_mr for full detail. Defaults: 20 per page.
gitlab_list_pipelinesreadList CI pipelines for a GitLab project as slim rows { id, status, ref, sha (12 chars), source, updated_at, web_url }, newest first. Optional
gitlab_list_projectsreadList the authenticated user
gitlab_list_releasesreadList releases for a GitLab project as slim rows { tag_name, name, released_at, upcoming }, newest first. Defaults: 20 per page.
gitlab_merge_mrwriteMerge a merge request into its target branch. IRREVERSIBLE: this lands the commits — check state and merge_status with gitlab_get_mr first, and only merge when explicitly asked. Optional: merge_commit_message, squash, remove_source_branch. Returns the merged MR
gitlab_retry_pipelinereadRetry the failed and canceled jobs in a GitLab pipeline by id. Returns the slim pipeline; on failure returns GitLab
gitlab_search_codereadSearch code (blobs) across the GitLab instance as slim rows { path, project_id, ref, startline, data }. Scope to a project with
gitlab_search_issuesreadSearch issues across the GitLab instance (or one project if
gitlab_search_projectsreadSearch projects across the GitLab instance by name/path as slim rows { id, path_with_namespace, visibility, default_branch, description }. Default 10 results. Use the id or path_with_namespace as
gitlab_trigger_pipelinewriteTrigger a new CI pipeline for a GitLab project on a branch or tag (
gitlab_update_issuewriteUpdate a GitLab issue — set any of title, description, labels, and close/reopen it. Pass state=
google_calendar_create_eventwriteCreate an event on a calendar (default \
google_calendar_delete_eventdestructiveDelete a calendar event permanently (attendees are notified per calendar settings). Returns { success: true } on success. To merely change time/details, use google_calendar_update_event instead.
google_calendar_freebusyreadCheck when calendars are busy between timeMin and timeMax (ISO 8601) — use this BEFORE google_calendar_create_event to pick a free slot and avoid double-booking. calendarIds defaults to [\
google_calendar_get_eventreadGet one calendar event by id. Returns the slim detail shape: { id, summary, start, end, location, status, organizer, htmlLink, description (truncated to 1000 chars), attendees: [{ email, responseStatus }] }. Get event ids from google_calendar_list_events.
google_calendar_list_calendarsreadList the user
google_calendar_list_eventsreadList events on a calendar (default \
google_calendar_update_eventwriteUpdate fields of an existing event (PATCH — only the fields you pass change). Returns the updated event in the same slim shape as google_calendar_get_event. To remove an event entirely use google_calendar_delete_event.
google_docs_batch_updatedestructiveApply batch updates to a Google Doc (insert text, delete content, format, etc.)
google_docs_createwriteCreate a new Google Doc
google_docs_getreadGet a Google Doc by ID (returns full document structure)
google_docs_get_plaintextreadGet a Google Doc as plain text by ID
google_docs_searchreadSearch for Google Docs in Drive
google_drive_create_folderwriteCreate a folder in Google Drive
google_drive_downloadreadDownload a file from Google Drive
google_drive_listreadList files in Google Drive, optionally filtered by a raw Drive query string (
google_drive_permissionswriteUpdate permissions for a Google Drive file
google_drive_trashwriteMove a file to trash in Google Drive
google_drive_uploadwriteUpload a file to Google Drive
google_drive_upload_from_urlwriteFetch a file from a signed or pre-authenticated URL (e.g. S3, GCS, CDN) and upload it directly to Google Drive — no need to pass file content inline. MIME type is auto-detected from the response Content-Type header when not specified. Returns the created Drive file { id, name, mimeType }.
google_gemini_generatereadGenerate content with Google Gemini
google_gmail_draftwriteCreate a draft (saved to the Drafts folder, NOT sent — use google_gmail_send to actually send).
google_gmail_getreadRead one message in full: decoded plain-text body plus from/to/subject/date and messageIdHeader
google_gmail_labelsreadList all label IDs and names (system labels like INBOX/UNREAD/STARRED plus user labels).
google_gmail_listreadList messages with sender/subject/date/snippet per row — use this for scanning a mailbox; use
google_gmail_modifywriteChange a message
google_gmail_profilereadGet the connected account
google_gmail_sendwriteSend an email, or reply within an existing conversation. To reply in-thread you must pass BOTH
google_gmail_threadsreadList conversation threads as {id, snippet, historyId} rows plus nextPageToken — one row per
google_sheets_appendreadAppend rows to the end of a table/log in a Google Sheet — THIS is the tool for adding new rows (Sheets finds the table in
google_sheets_createwriteCreate a new (empty) Google Spreadsheet with the given title, optionally with named tabs (
google_sheets_readreadRead cell values from a Google Sheet range (e.g. \
google_sheets_searchreadFind Google Sheets by name (Drive name-contains match). Returns up to pageSize (default 10) rows of { id, name, modifiedTime } plus nextPageToken — use the id with google_sheets_read/write/append. For full-text or non-spreadsheet search use google_drive_search.
google_sheets_writedestructiveOVERWRITE cell values in an exact range of a Google Sheet (PUT semantics — existing cells in the range are replaced). To add new rows to the end of a log/table WITHOUT touching existing data, use google_sheets_append instead. Returns { updatedRange, updatedCells }.
google_slides_batch_updatedestructiveApply batch updates to a Google Slides presentation (insert text, create/delete slides, etc.)
google_slides_createwriteCreate a new Google Slides presentation
google_slides_create_from_markdownwriteCreate a Google Slides presentation from Markdown text. Use
google_slides_getreadGet a Google Slides presentation by ID
google_slides_searchreadSearch for Google Slides presentations in Drive
httpbin_get_cookiesreadGet current cookies from httpbin
httpbin_get_headersreadGet request headers (useful to verify Cookie header is injected)
httpbin_set_cookiewriteSet a cookie on httpbin
jira_add_commentwriteAdd a comment to a Jira issue; body is plain text (wrapped in ADF automatically). Returns { id, created } of the new comment — use jira_get_comments to read the thread.
jira_create_issuewriteCreate a Jira issue and return its { id, key, self }. description is plain text (wrapped in ADF automatically); issueType defaults to \
jira_get_boardsreadList Jira agile boards as { boards: [{ id, name, type, projectKey }], total, isLast }; optionally filter by projectKey (maxResults defaults to 50). Requires the read:board-scope:jira-software scope — reconnect Jira if this returns 401.
jira_get_commentsreadRead the comments on a Jira issue as slim rows { id, author, body (plain text extracted from ADF), created }, oldest first (maxResults defaults to 10). Use jira_add_comment to reply.
jira_get_create_metawriteReturn the fields required (and allowed) when creating a Jira issue. Pass projectKey and optionally issueType to scope the result. Each field entry includes { fieldId, name, required, schema, allowedValues } — use the fieldId as the key and an allowedValues entry
jira_get_issuereadGet a Jira issue
jira_get_transitionsreadList the status transitions currently available for an issue as [{ id, name, toStatus }] — transitions depend on the issue
jira_list_projectsreadList the Jira projects you can see as slim rows { key, id, name, projectTypeKey } (maxResults defaults to 25); pass query to filter by name/key. The standard way to discover a projectKey for jira_create_issue or JQL.
jira_project_typesreadList the Jira project types available on this site as slim rows { key, formattedKey, type } (icons stripped). Rarely needed — use jira_list_projects to discover actual projects and their keys.
jira_search_issuesreadSearch Jira issues with a JQL query; returns slim rows { key, id, summary, status, assignee, priority, updated } plus nextPageToken for paging (maxResults defaults to 10). Pass fields to request other fields (returned raw under each row
jira_search_usersreadSearch Jira users by name or email; returns slim rows { accountId, displayName, emailAddress, active } (maxResults defaults to 10). Use the accountId with jira_update_issue
jira_transition_issuewriteMove a Jira issue to another status by applying a workflow transition; returns { success: true }. transitionId must come from jira_get_transitions for the same issue (ids vary per workflow and current status).
jira_update_issuewriteUpdate a Jira issue
linear__create_issuewriteCreate a Linear issue
list_integrationsreadList all available integrations and connection status
list_jot_filesreadList the files inside a jot you own (path, bytes, updatedAt), so you can see the current structure before making a partial update with update_jot. Returns FORBIDDEN if another user owns it, NOT_FOUND if it doesn
list_jotsreadList the jots you have deployed (name, access, url, updatedAt). Only your own jots are returned.
newrelic_add_notification_channels_to_policywriteAssociate existing (legacy) notification channels with an alert policy so it sends notifications when incidents open (alertsNotificationChannelsAddToPolicy). Provide the policy ID and one or more channel IDs.
newrelic_add_tags_to_entitywriteAdd tags (key + values) to a New Relic entity for filtering and organization (taggingAddTagsToEntity). Identify the entity by its GUID. Note: for APM agents a restart may be required before new tags appear. Returns { errors } (empty on success).
04

Trust audit

BLOCKgrade F · trust 34/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryWARN
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)WARN
L3Class-specific surfaceWARN
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (11 observation(s))
Network
declared (10 observation(s))
Shell
declared (2 observation(s))
Dependencies
not all pinned
Secrets in source
found

Findings (25)

HIGHCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/server/src/db-adapter.ts:30
exec(sql: string): Promise<void>;
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
HIGHCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/server/src/types/better-sqlite3.d.ts:4
exec(sql: string): void;
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
HIGHNetwork egress · net.metadata_ip · CWE-200, CWE-319
packages/server/src/auth/plugin-oauth.ts:63
if (a === 169 && b === 254) return true; // link-local (incl. cloud metadata 169.254.169.254)
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
MEDIUMInventory / provenance · inv.binary · CWE-1104
packages/server/src/jots/auth.ts
auth.ts
Why it matters. a compiled or binary member cannot be reviewed from source
Fix. ship source, or explain the binary in the README
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
packages/server/capture-user.ts:12
console.log(token);
MEDIUMInformation disclosure · disclose.log_secret · CWE-209, CWE-532
packages/server/scripts/seed-local-user.ts:21
console.log(`api key: ${apiKey}`);
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/ci.yml:95
TEST_POSTGRES_URL: postgres://postgres:postgres@localhost:5432/workbench_test
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
docs/findings/2026-08-06-postgres-dialect-gotchas.md:106
TEST_POSTGRES_URL=postgres://postgres:[email protected]:5432/postgres npm run test -w @a-workbench/server
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
docs/site/_content/deploy/database.md:12
DATABASE_URL=postgres://user:[email protected]:5432/workbench    # PostgreSQL
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
docs/site/_content/deploy/database.md:174
export TARGET_DATABASE_URL=postgres://user:[email protected]:5432/workbench
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
packages/server/tests/db-adapter.integration.test.ts:30
"TEST_POSTGRES_URL=postgres://postgres:[email protected]:5432/postgres to cover the PostgreSQL path."
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
packages/server/tests/meta-tools.test.ts:1241
const SECRET = "tok-abc-secret-value";
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
packages/server/tests/routes.test.ts:1865
const SECRET = "tok-abc-secret-value";
MEDIUMHard-coded secrets · secret.slack · CWE-798, CWE-321
packages/server/tests/plugin-oauth.test.ts:473
access_token: "xoxp-user-token",
MEDIUMHard-coded secrets · secret.slack · CWE-798, CWE-321
packages/server/tests/plugin-oauth.test.ts:482
accessToken: "xoxp-user-token",
MEDIUMHard-coded secrets · secret.slack · CWE-798, CWE-321
packages/server/tests/plugin-oauth.test.ts:494
access_token: "xoxe.xoxp-user-token",
MEDIUMHard-coded secrets · secret.slack · CWE-798, CWE-321
packages/server/tests/plugin-oauth.test.ts:502
expect(callArgs.accessToken).toBe("xoxe.xoxp-user-token");
MEDIUMPrompt injection · prompt.hidden_style · CWE-94, CWE-1427
docs/superpowers/plans/2026-09-06-brand-and-landing.md:463
`<span style="color:#fff;font-size:120px;font-weight:800;letter-spacing:-.03em">workbench</span></div>`,
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
clevertap_get_uninstall_report, confluence_delete_page, delete_jot, files_delete, google_calendar_delete_event, google_docs_batch_update, google_sheets_write, google_slides_batch_update, slack_remove_
Why it matters. 9 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.binary · CWE-1104
packages/server/tests/workspace-paths.test.ts
workspace-paths.test.ts
Why it matters. a compiled or binary member cannot be reviewed from source
Fix. ship source, or explain the binary in the README
LOWInventory / provenance · inv.hidden_file · CWE-1104
docs/site/.nojekyll
.nojekyll
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/server/tests/reconnect-dom.test.ts:117
const f = new Function("location", "document", "isSecureContext", "HTMLInputElement", "HTMLTextAreaElement", "Event", `return (${fn});`)(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/server/tests/reconnect-dom.test.ts:140
const f = new Function("location", "document", "isSecureContext", "HTMLInputElement", "HTMLTextAreaElement", "Event", `return (${fn});`)(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/plugins/clevertap/tools/index.ts:138
const { storeToken } = await import("../../../server/src/auth/tokens");
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/portal/src/components/admin/ActivityCard.test.tsx:3
import { renderWithClient } from "../../test-utils";

Gates applied: no_behavioural_pass.

Audited 2026-10-13 · audit v0.4.1 · source sha 7477d052587dfull audit observations/trust-audit/mcp-server/amarthaid__workbench.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-137477d052587dBLOCKF34first audit
06

Questions

What is the workbench MCP server?

One MCP endpoint. Every tool your agent needs. Self-hosted, per-user OAuth. 20+ SaaS integrations and invite you to add more.

What tools does workbench expose?

200 in total: 140 read-only, 87 that write, and 9 that can delete or overwrite (clevertap_get_uninstall_report, confluence_delete_page, delete_jot, files_delete, google_calendar_delete_event). Every one is listed on this page with its risk.

Is workbench safe to connect to an agent?

No — not without reading the findings first. The audit graded it F (34/100) and found 3 critical or high issues in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 9 of its tools can destroy data, so scope the token you give it to what you actually need.

What credentials does workbench need?

It reads CAPTURE_PROXY_PASSWORD, GITHUB_CLIENT_SECRET, GITLAB_CLIENT_SECRET, GOOGLE_GMAIL_CLIENT_SECRET, NEW_RELIC_API_KEY, PGPASSWORD, SESSION_SECRET and SLACK_CLIENT_SECRET from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does workbench run?

It speaks streamable-http, so it runs as a service you connect to over the network. It is published on npm as @a-workbench/site at 0.1.0.

How current is this page?

The grade is for one exact copy of the source (7477d052587d), read on 2026-10-13. The repository is watched and re-audited when it changes.

Advertisement