@chrischall/concur-mcp

中文文档
0.5.0发布于 2026/11/07MITES Module构建来源已验证近 30 天下载 2256

Unofficial SAP Concur MCP server for Claude — expense reports, expenses, receipts and trips — developed and maintained by AI (Claude Code)

原始使用文档
文档目录22 节
  1. What you can do
  2. Requirements
  3. Installation
  4. Claude Code plugin
  5. Any MCP host (npx)
  6. Claude Desktop (.mcpb)
  7. How it authenticates
  8. Pairing (once)
  9. Configuration
  10. Confirming writes
  11. Tools
  12. Session and lookups
  13. Reports and expenses (read)
  14. Reports and expenses (write — all confirmation-gated)
  15. Attendees
  16. Receipts
  17. Travel
  18. Escape hatches
  19. Out of scope
  20. Upgrading
  21. Development
  22. License

SAP Concur MCP

CI npm license

A Model Context Protocol server that connects Claude to SAP Concur: your expense reports, the expenses on them, available (unassigned) expenses, receipts, and Concur Travel trips — read and write, with every write confirmation-gated.

[!WARNING] AI-developed project. This codebase was entirely built and is actively maintained by Claude. No human has audited the implementation. Review all code and tool permissions before use.

[!CAUTION] Unofficial and unsupported. This server does not use Concur's documented partner API. It calls the internal GraphQL API the Concur web app itself uses, authenticated with your own signed-in browser session. It may break without notice, and automated use may conflict with SAP Concur's or your employer's terms. Use it only for your own account, at your own discretion.

What you can do

Ask Claude things like:

  • "Which of my Concur reports are still unsubmitted, and what's blocking them?"
  • "Show me the card transactions that aren't on a report yet."
  • "Create a report called 'Austin offsite' and move last week's Uber and hotel charges onto it."
  • "Upload ~/Downloads/dinner.pdf and attach it to the dinner expense."
  • "When does my next trip leave, and what hotel am I in?"

Requirements

Installation

Claude Code plugin

claude plugin marketplace add chrischall/concur-mcp
claude plugin install concur@chrischall

Any MCP host (npx)

{
  "mcpServers": {
    "concur": {
      "command": "npx",
      "args": ["-y", "@chrischall/concur-mcp"],
      "env": { "CONCUR_DC": "us2" }
    }
  }
}

Claude Desktop (.mcpb)

Install the .mcpb bundle from the latest release. The installer asks for your datacenter (default us2).

How it authenticates

Concur's web app authenticates its API calls with an HttpOnly JWT cookie that the page itself cannot read. This server uses the fetchproxy bridge only to read three session cookies from your signed-in Concur tab: JWT, plus Concur's legacy session cookies OTSESSIONAABQRD and OTSESSIONAABQRN. Every API call is then made from Node with Authorization: Bearer <jwt> and a Cookie header carrying whichever OTSESSION cookies the tab had — the parts of Concur still served by its legacy services (report keys, currencies, available receipts, permissions) answer an error without them. The cookies are held in memory only and never appear in an error or a log; the only thing written under your home directory is the bridge's pairing identity.

The token lives 60 minutes. The server re-reads the cookies (all together) from your tab when the token is within two minutes of expiry, or once on a 401 (one re-read and one retry per call, never a loop). A read that fails the way a stale OTSESSION cookie fails gets the same single re-read and retry. Concur keeps the cookies fresh while a tab is open, so keep a signed-in Concur tab open while you use the tools. concur_healthcheck reports which source supplied the token, how many minutes it has left and whether the legacy session cookie was found — never a cookie value.

Upgrading from an earlier build? The bridge scope grew from JWT alone to JWT + the two OTSESSION cookies. The pair grant is per-scope, so an already-paired server must be re-approved once: the next call answers with a new pair code (or a scope-changed notice) — approve it in the ContextMint Bridge popup.

Pairing (once)

  1. Install ContextMint Bridge (Chrome: load the release zip unpacked) and sign in to Concur in that browser.
  2. Ask Claude to run concur_healthcheck. The first call answers with a pair code.
  3. Approve that code in the ContextMint Bridge popup. The trust persists across restarts (it lives in ~/.fetchproxy/identity/).

More than one browser? If ContextMint Bridge is installed in several browsers, each one dials the same local port, and the pair code can be approved in any of them. Approve it in the browser that holds your Concur session — the server pins whichever extension approved it, and only that browser's tab can supply the cookie. Lines like refusing an extension whose identity is not the one this MCP paired with on stderr are the other browser being turned away; they are harmless.

Paired the wrong browser, or changed browsers? Revoke concur-mcp in the ContextMint Bridge popup, clear the server's pinned extension with fpx trust clear concur-mcp (from @fetchproxy/cli; or delete ~/.fetchproxy/identity/concur-mcp.extension-trust.json), restart the server, and pair again from the right browser.

Configuration

Env varDefaultPurpose
CONCUR_DCus2Your datacenter: the first label of your signed-in tab's host (us2 for us2.concursolutions.com, eu2, …). Must match the session token's issuer, or every call fails with an error that says which value to use.
CONCUR_WS_PORT37149The fetchproxy bridge port. The whole fetchproxy fleet and ContextMint Bridge share 37149; override only for local testing (or when a host injects a per-registration port).
CONCUR_OUTPUT_DIR~/Downloads/concur-mcpWhere concur_get_receipt saves downloaded receipts (never overwriting an existing file).
CONCUR_UPLOAD_ROOTS~/Downloads, ~/Documents, ~/Desktop, plus the cwd when it is a folder inside your home directory (never / or home itself — Claude Desktop starts the server in /)The only folders concur_upload_receipt will read from (a path-delimiter-separated list; ~ expanded). A hosted server refuses every path (use concur_upload_slots or inline).
CONCUR_INLINE_RECEIPTSoff locally, on when hostedtrue makes concur_get_receipt return the image in the result instead of writing a file (over 10 MiB it is saved locally, or a signed link when hosted).
MCP_USER_TZthe server's zoneYour IANA time zone (e.g. America/New_York), used for "today" when concur_create_report defaults reportDate. Set it when hosted — a hosted server runs in UTC, so an evening report would otherwise be dated tomorrow.
CONCUR_DEBUGoffLog the bridge's role and lifecycle to stderr.
MCP_CONFIRM_MODEask-userWhat a write does on a client that cannot show a confirmation prompt — see below.
MCP_CONFIRM_ELICITATIONonoff never shows a confirmation prompt, so every client takes the MCP_CONFIRM_MODE path (for clients that declare prompts but never render them).

Confirming writes

Every tool that changes something in Concur (or emails someone) is confirmation-gated:

  • Clients that support MCP elicitation show a real confirmation prompt describing exactly what will be sent.
  • Elsewhere (claude.ai, Claude Desktop) the first call makes no change: it returns a preview of exactly what would be sent plus a single-use, short-lived confirmToken. Only a repeat call carrying that token writes, and if anything in the request changed in between, the token is refused.

MCP_CONFIRM_MODE decides who may use that token:

ValueBehaviour
ask-user (default)The model must show you the preview and get your approval in chat before reusing the token.
autoThe model may use the token itself after seeing the preview.
refuseWrites are refused on clients without a confirmation prompt. An unrecognised value is treated as refuse.

Text that comes back from Concur — vendor names, comments, trip details, emails — is treated as untrusted data: it can never authorise a write.

Tools

Most read tools take view: compact | full | raw (default compact): compact drops the fields you rarely need, full keeps them all, raw is Concur's response unprojected.

Session and lookups

ToolWhat it does
concur_healthcheckWhich source supplied the token, whether the API accepted it, round-trip time, minutes to expiry
concur_whoamiYour Concur user id, datacenter, token minutes left, and enabled features
concur_list_expense_typesExpense types a report's policy allows (id, name, group)
concur_list_payment_typesPayment types you can put on an expense (cash, company card, …)
concur_list_currenciesCurrencies Concur accepts, optionally filtered
concur_search_locationsExpense locations by city (optionally country / subdivision)

Reports and expenses (read)

ToolWhat it does
concur_list_reportsYour reports by status (ALL, ACTIVE, UNSUBMITTED, SENT_FOR_PAYMENT) and date range
concur_get_reportOne report: header, every expense entry, and its exceptions (blocking ones prevent submission)
concur_get_report_timelineA report's timeline (comments and workflow events) and audit trail
concur_get_expenseOne expense: form fields by label, itemizations, receipt, comments, exceptions
concur_list_available_expensesCard transactions, e-receipts and mobile captures not yet on a report

Reports and expenses (write — all confirmation-gated)

ToolWhat it doesReversible?
concur_create_reportCreate an unsubmitted reportyes (concur_delete_report)
concur_update_reportChange header fields; sends only what changesyes
concur_delete_reportDelete an unsubmitted report and every expense on itno
concur_add_report_commentAdd a comment approvers will seeno (comments cannot be deleted)
concur_submit_reportSend a report to your approverno — it reaches a person; concur_recall_report can pull it back before approval
concur_recall_reportRecall a submitted, unapproved reportyes
concur_create_expenseAdd an expense to an unsubmitted report; any editable form field by label (custom ones, Ticket Number, From/To Location, …); a foreign currency gets Concur's rate for the dateyes
concur_update_expenseChange an expense, including those form fields and the exchange rate; sends only what changesyes
concur_delete_expensesDelete expenses from a report (card transactions return to available expenses)no
concur_copy_expenseDuplicate an expenseyes
concur_move_available_expenses_to_reportMove available expenses onto a reportno (Concur has no move-back)
concur_delete_available_expensesDelete available expensesno

Attendees

ToolWhat it doesReversible?
concur_search_attendeesSearch the attendee directory (colleagues, saved guests) by name or emailread
concur_set_attendeesReplace an expense's attendee list (you first by default), splitting the amount evenly — confirmation-gatedno (no tool reads the previous list back, or clears it)

Receipts

ToolWhat it doesGated?
concur_list_available_receiptsReceipt images in your receipt store that are not attached to an expenseread
concur_get_receiptDownload a receipt image (≤ 25 MB): to CONCUR_OUTPUT_DIR locally, inline, or — hosted and over 10 MiB — a signed download link valid for an hourno (nothing changes in Concur)
concur_upload_slotsSigned upload URLs (with a ready curl) for sending receipt files to a hosted serverno (signs URLs only)
concur_upload_receiptUpload a png / jpg / pdf (≤ 25 MB) from an allowed local folder (path, local installs only), as base64 (inline), or from an upload slot (blobKey)yes
concur_attach_receiptAttach a stored image to an expense that has noneyes
concur_append_receiptAdd a stored image as an extra page of an expense's receiptyes
concur_detach_receiptTake the image off an expenseyes
concur_add_missing_receipt_declarationMissing Receipt Declaration (affidavit) for expenses with no receipt — you certify the receipt is missing; the preview shows your policy's declaration text. No tool removes one (attaching a receipt replaces it)yes
concur_delete_receiptDelete an unattached receipt image — cannot be undoneyes

Travel

ToolWhat it does
concur_list_tripsYour trips (upcoming by default; past or all), filterable
concur_get_tripOne trip with its flight, hotel, car and rail bookings
concur_get_trip_historyA trip's history: changes, approvals, itinerary emails
concur_send_itineraryEmail a trip's itinerary — sends real email, confirmation-gated

Escape hatches

ToolWhat it does
concur_list_operationsThe GraphQL operations the Concur web app uses — name, endpoint, purpose, and which dedicated tool already runs it
concur_graphql_queryRun a raw read-only query (spend or cds endpoint); refuses anything that could write
concur_graphql_mutationRun a raw mutation, confirmation-gated with the full document in the preview; refuses the operations listed under Out of scope, including submit (use concur_submit_report)

Out of scope

These exist in Concur but are deliberately not built. Not built is not the same as refused: the raw escape hatches can still reach anything below that the guard does not name.

Refused by concur_graphql_mutation (matched by operation name and by the field the web app's text selects, so renaming or aliasing does not get through): processApproval, updateWorkItemStatus, startSearch, saveBookingSelections, holdTrip, confirmTrip, commitChange, tryCancelTripOrBooking, and SubmitExpenseReport (use concur_submit_report).

  • Approvals — approving or sending back other people's reports (contextRole: MANAGER). Not built: the approvalsportal.asp page, the GetApproversList and loadApprovalDetails reads, and the UpdateTimelineWorkflow call. Refused: CDS processApproval, and updateWorkItemStatus (updateDelegatesDashboardWorkItemStatus). This server acts only on your own data.
  • Card-transaction management — the card accounts page and its operations: /Expense/Client/cardtransactions.asp, GetCardAccounts, GetCardTransaction, MoveCCTransactionsToReport, CreateCBSReportAndMoveCCTransactionsToReport, RefreshYodleeTransactions. (Card charges that already appear as available expenses are covered — list, move to a report, delete.)
  • Booking or cancelling travel — search, price, book, hold, confirm or cancel (startSearch, saveBookingSelections, holdTrip, confirmTrip, commitChange, tryCancelTripOrBooking). Trips are read-only apart from emailing the itinerary.

Upgrading

0.5.0 — concur_get_receipt inline output. A non-image receipt returned inline (a PDF) is an embedded resource whose URI is now attachment:///receipt-<imageId>.<ext> (it was concur-receipt:<imageId>). The JSON block also gains via (image, resource, link or disk), and on a hosted server downloadUrl + expiresAt for a signed link. A caller that matched the old concur-receipt: URI should read via instead.

Development

npm install
npm run build        # tsc → dist/, then esbuild → dist/bundle.js (the .mcpb entry)
npm test             # typecheck + vitest
npm run test:coverage

The API surface this is built on — hosts, auth, every operation used, and what was verified live — is in docs/CONCUR-API.md, with verbatim operation texts in docs/api/.

License

MIT

版本与发布记录

@chrischall/concur-mcp 共发布 13 个版本,首次发布于 2026/10/09;过去 12 个月发布了 13 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.5.0latest2026/11/072 天前
0.4.12026/11/054 天前
0.4.02026/11/055 天前
0.3.42026/10/2911 天前
0.3.32026/10/2119 天前
0.3.22026/10/1823 天前

关于 @chrischall/concur-mcp 的常见问题

@chrischall/concur-mcp 是什么?

Unofficial SAP Concur MCP server for Claude — expense reports, expenses, receipts and trips — developed and maintained by AI (Claude Code)

如何安装 @chrischall/concur-mcp?

在项目目录运行 npm install @chrischall/concur-mcp,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 concur-mcp,可以用 npx concur-mcp 直接运行。

@chrischall/concur-mcp 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/chrischall__concur-mcp 是否存在。

@chrischall/concur-mcp 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@chrischall/concur-mcp 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.5.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@chrischall/concur-mcp 可以商用吗?

@chrischall/concur-mcp 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@chrischall/concur-mcp 的下载量有多少?

根据 npm 官方下载统计,@chrischall/concur-mcp 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,256 次,日均约 75 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chrischall/concur-mcp 还在维护吗?

最新版本 0.5.0 发布于 2026/11/07(2 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/10/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。