@mgsoftwarebv/mcp-server-bridge
v3.5.36
Published
MCP Server bridge for MG Tickets - connects Cursor to HTTP MCP server with image support and GitHub code exploration
Maintainers
Readme
@mgsoftwarebv/mcp-server-bridge
An MCP server that connects Cursor / Claude Desktop / Windsurf directly to your Refront Postgres database, exposing tickets, customers, projects, documents and time tracking as MCP tools and resources.
Since v3.0 the bridge talks straight to Postgres via Drizzle ORM — the previous PostgREST/JWT transport has been removed.
📦 Installation
npm install -g @mgsoftwarebv/mcp-server-bridge🔧 Cursor Configuration
Add the following to your ~/.cursor/mcp.json. You always need two things:
- An API key from your Refront dashboard (
/settings/developer, formatmid_...). - A Postgres connection string for your Refront database (
DATABASE_URL).
{
"mcpServers": {
"refront": {
"command": "npx",
"args": [
"-y",
"@mgsoftwarebv/mcp-server-bridge@latest",
"--api-key=mid_your_api_key_here",
"--database-url=postgresql://USER:PASSWORD@HOST:PORT/DBNAME"
]
}
}
}Both values can also be supplied via environment variables instead of CLI flags:
{
"mcpServers": {
"refront": {
"command": "npx",
"args": ["-y", "@mgsoftwarebv/mcp-server-bridge@latest"],
"env": {
"MG_TICKETS_API_KEY": "mid_your_api_key_here",
"DATABASE_PRIMARY_POOLER_URL": "postgresql://USER:PASSWORD@HOST:PORT/DBNAME"
}
}
}
}🔑 Getting an API Key
- Go to your Refront dashboard:
/settings/developer - Create a new API key
- Copy the key (format:
mid_...) - Use it in your MCP configuration
🗄️ Getting the Database URL
- Refront SaaS customers: ask MG Software for a read-only Postgres user that points at your team's database (we do not hardcode a default connection string here for security reasons).
- Self-hosted: use the same
DATABASE_PRIMARY_POOLER_URLyou configured for the API/dashboard.
The bridge uses @refront/db/job-client, which is tuned for a single connection per process and gracefully closes idle connections.
📋 Command-Line Options
# Basic usage
mg-tickets-mcp --api-key=mid_your_key --database-url=postgresql://...
# Via environment variables
export MG_TICKETS_API_KEY=mid_your_key
export DATABASE_PRIMARY_POOLER_URL=postgresql://USER:PASSWORD@HOST:PORT/DBNAME
mg-tickets-mcp--database-url falls back to DATABASE_PRIMARY_POOLER_URL, then to DATABASE_URL. The bridge exits at startup if no connection string is provided.
For attachment downloads you also need to expose Cloudflare R2 credentials (R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, optional R2_PUBLIC_DOMAIN). Without them the attachment / image inlining tools will return an error, but everything else keeps working.
🏢 Multi-provider support
A single API key now works even if you belong to multiple providers (workspaces). The key is bound to one provider as its default, but every team-scoped tool accepts an optional teamId argument that is validated against your team memberships.
How it resolves which provider a tool acts on:
teamIdprovided → it is validated against your memberships and used.teamIdomitted, you belong to one provider → that provider is used automatically (no change for single-provider users).teamIdomitted, you belong to several → list/create tools return the list of your providers and ask the AI to re-call with ateamId. By-id tools resolve across all your providers (the record id disambiguates), and acceptteamIdto narrow.
Use get-teams to list the providers you can act on and copy a teamId.
🛠️ Available Tools
Related mutations are consolidated into a single action-based tool (pick the
operation with an action argument), while reads and destructive deletes stay
their own explicit tools. Full parameter docs live in each tool's MCP schema
(src/tools/definitions/).
Teams
- get-teams — list the provider teams (workspaces) you can act on; use a returned id as
teamIdon other tools
Tickets, Customers & Projects
- get-tickets — list tickets with filters (status, priority, project, customer, text query); each result includes the ticket ID (UUID) and ticket number
- get-ticket-by-id — fetch one ticket by UUID or ticket number, with all comment text, a full attachment listing (ids), and inline images (base64)
- create-ticket — create a new ticket (returns UUID and ticket number)
- update-ticket — update fields (title, description, status, priority, type, project, customer, assignee, estimated hours, deadline, tags); accepts UUID or ticket number;
assigneeId: nullunassigns - add-ticket-comment — add a comment (plain text or TipTap JSON); accepts UUID or ticket number;
isInternalfor internal-only notes - get-ticket-attachment / upload-ticket-attachment / delete-ticket-attachment — signed download URLs, uploads, and removals
- get-customers — list/search customers
- customer (
action: create|update|archive) — create, edit, or archive a customer; delete-customer hard-deletes an empty one - get-customer-agreements + customer-agreement (
action: create|update) — per-customer product agreements (draft-only writes) - get-projects — list/search projects
- project (
action: create|update|archive) — create, edit, or archive a project; delete-project hard-deletes an empty one - project-members (
action: list|set|add|remove) and project-tags (action: list|set|add|remove) - get-tags + tag (
action: create|update|merge) + delete-tag - get-calendar-items + calendar-item (
action: create|update)
Note: Ticket writes (
update-ticket,add-ticket-comment) are recorded in the dashboard's ticket activity feed but do not send notifications (email/Slack/in-app) or trigger@mentionrouting. Notification delivery is a planned follow-up.
Quote / Proposal Documents
- list-documents — list documents with filters (type, status, customer, title search)
- get-document — fetch one document with metadata and the full blocks JSON; pass
linkType(dashboard/pdf_signed/public_share) to get only a shareable link - document (
action: create|update) — create from content blocks (cover, toc, heading, text, callout, table, chart incl. multi-series, pricing, timeline, two_column, divider, signature) or update (replace/append/targetedblockOps); supports ateamIdprovider override and links to acustomerId/invoiceId - delete-document — soft-delete a document
Invoicing, Products & Quotes
- get-invoices — list invoices, or pass
invoiceIdfor full detail (andlinkTypefor a shareable/PDF/payment link) - update-invoice — edit a draft invoice:
lineItems(replace all),patchLines(targeted),addProduct(add a catalog product), orlinkDocumentId(attach a document) - send-invoice-to-customer — email an invoice via the dashboard send pipeline
- get-products — list catalog products, or pass
productIdfor one; product (action: create|update|archive) - get-quotes + quote (
action: create|update|add-product) — draft-only quote/offerte writes
Trips (kilometer registration)
- get-trips + trip (
action: create|update) + get-trip-context (include: vehicles|templates|frequent)
Document text runs through a built-in humanizer that strips AI-sounding patterns (em-dashes, NL/EN clichés like "naadloos"/"seamless", filler phrases). Control it per call with humanize:
"rules"(default) — deterministic cleanup, no API key needed; the tool result includes a change report"full"— rules + an LLM rewrite pass (requiresOPENAI_API_KEYin the bridgeenv)"off"— store the blocks exactly as provided
Optional PDF compilation: pass compilePdf: true to trigger the compile-document-pdf Trigger.dev job after create/update. This requires TRIGGER_SECRET_KEY (and optionally TRIGGER_API_URL) in the bridge env; without it the dashboard compiles the PDF when the document is opened or shared.
Time Tracking
- log-hours — log work hours as a draft agenda event (see the
/hoursCursor command) - create-time-entries — create one or more individual dated hour lines (one timesheet event per date), single or bulk via
entries[]. Top-levelprojectId/ticketId/userId/description/billable/statusare shared defaults each entry can override. Skips duplicates (same user/team/date/project/description) unlessallowDuplicate, and reports created/skipped/errored lines plustotalHours. Use this instead oflog-hourswhen you have separate dated lines (e.g. "8h on 19/6, 8h on 16/6"). - get-time-entries — read and total tracked time entries (urenregistratie), filterable by period, user, project, customer, ticket, status and type; returns accurate aggregate totals (total/billable/non-billable/invoiced hours) plus an optional grouped breakdown (day/project/customer/user/ticket). Answers questions like "how many hours did I work this month for customer X?"
- delete-time-entries — soft-delete one or more existing hour lines by
id/ids[](e.g. to drop a wrongly-logged lump entry before re-creating dated lines). Only draft, non-invoiced entries are removed unlessallowInvoicedOverride; confirmed/invoiced ones are skipped. Reports deleted/skipped/errored ids plusdeletedHours. - update-time-entry — correct a single hour line by
id: changedate/hours(re-times the line),description,projectId,ticketId,statusorbillable. On confirmed/invoiced entries the financial/time fields are locked unlessallowInvoicedOverride; description and project/ticket links stay editable.
GitHub Code Exploration
- github (
action: read-file|list-dir) — read one file, or list a directory, in the GitHub repo linked to a project
Activity, Telemetry & Cycle-time Reports
- get-activity-report (
report: user|mcp|ui|ticket-timeline|ticket-analytics) — daily per-user execution timeline, Refront MCP/UI telemetry, and per-ticket / cross-ticket cycle-time metrics; actor-based and timezone-aware
⏱️ Quick Hour Logging with /hours
The /hours Cursor command makes logging time effortless:
- Do your work in Cursor — chat and code normally.
- Type
/hourswhen you're done. - The AI analyzes the chat and estimates how long a senior developer (without AI) would have spent.
- Workspace ↔ project matching is automatic where possible.
- A draft entry is created in the tracker (not billed yet).
How it works
- Lists all projects via
get-projects. - Matches the workspace folder name to the closest project (or skips matching when ambiguous).
- Analyzes the chat — what was built/fixed.
- Estimates a realistic time budget (investigation + implementation + testing).
- Creates a tracker entry as draft.
📚 Available Resources
- tickets://recent — most recent tickets across all accessible teams
- customers://all — all accessible customers
- projects://active — all accessible projects
🔍 Debugging
The bridge logs to stderr so you can capture debug output without breaking the MCP stdio protocol:
mg-tickets-mcp --api-key=mid_... --database-url=postgresql://... 2>debug.logDebug output shows:
- Database connection / startup status
- API key validation hash
- Tool dispatch and access decisions
- Errors with stack traces
🏗️ Development
git clone https://github.com/mgsoftwarebv/tickets-v2.git
cd tickets-v2/packages/mcp-server-bridge
bun install
bun run build
node dist/index.js --api-key=mid_... --database-url=postgresql://...🔄 Updates
npm update -g @mgsoftwarebv/mcp-server-bridge
# Or via npx (no install required)
npx @mgsoftwarebv/mcp-server-bridge@latest --api-key=mid_...🆘 Troubleshooting
❌ "API key is required"
- Pass
--api-key=or setMG_TICKETS_API_KEY. - Make sure the key has the format
mid_...(length 68).
❌ "Database URL is required"
- Pass
--database-url=postgresql://...or setDATABASE_PRIMARY_POOLER_URL/DATABASE_URL. - Confirm the user can reach the database from your machine (firewall, VPN, etc).
❌ "API key not found or invalid"
- Generate a new API key in
/settings/developerand check it is enabled. - Confirm the key belongs to a team that exists in the database you're connecting to.
❌ "R2 storage is not configured"
- Image / attachment tools need
R2_ENDPOINT,R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY. Set them in theenvblock of yourmcp.jsonif you need attachment support.
📄 License
MIT © MG Software B.V.
