harbor-mcp-server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| HARBOR_DB_PATH | No | Database location. Use ':memory:' for a throwaway. | ./harbor.db |
| HARBOR_REVEAL_PII | No | Returns email, phone and address unmasked. | false |
| HARBOR_ALLOW_WRITES | No | Enables issue_refund and extend_trial. | false |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| harbor_list_tablesA | List every table an agent may read, with row counts and a one-line note on each. Call this first when you do not already know the schema. It is cheap and it prevents guessing. Args: none. Returns JSON: { "tables": [ { "name": string, "rows": number, "note": string } ], "pii_masked": boolean // true when email/phone/address columns come back partially redacted } Tables not listed here cannot be queried — harbor_run_query will reject them. |
| harbor_describe_tableA | Return the column list, types, nullability and primary key for one table. Args:
Returns JSON: { "table": string, "note": string, "columns": [ { "name": string, "type": string, "nullable": boolean, "primary_key": boolean, "masked": boolean } ] } "masked" marks columns whose values are partially redacted on the way out. Example: use before writing a query that filters on a column you have not seen yet. Error: returns an error listing valid tables if the name is not readable. |
| harbor_run_queryA | Run a read-only SQL query against the Harbor business database. Every query is parsed and checked before execution. It will be rejected if it:
A LIMIT is added if you omit one, and lowered if you exceed 500. Args:
Returns JSON: { "rows": object[], // result rows, PII columns partially masked "columns": string[], "row_count": number, "tables_read": string[], "limit_applied": number, "limit_adjusted": boolean, // true if we added or lowered your LIMIT "truncated": boolean, // true if the cap was hit and more data exists "masked_columns": string[], "duration_ms": number } Examples:
Errors are returned with a "How to fix" line. Read it — it names the specific column, table or clause that caused the rejection. |
| harbor_find_customerA | Search customers by company name, contact name, email or id. Use this to turn a human's phrasing ("the Kestrel account") into a customer id before calling harbor_customer_360. Args:
Returns JSON: { "matches": [ { "id": string, "company_name": string, "contact_name": string, "email": string, "country": string, "status": "active" | "churned", "signed_up_at": string } ], "count": number } Emails are partially masked. Matching still works on the unmasked value, so searching "priya" finds her even though the response shows "pr***@...". Returns an empty match list, not an error, when nothing matches. |
| harbor_customer_360A | Everything about one customer in a single call: profile, subscription, recent invoices, open tickets and 90-day usage totals. Use this instead of four separate queries when a human asks about an account. Args:
Returns JSON: { "customer": { id, company_name, contact_name, email, country, industry, employee_count, signed_up_at, churned_at, churn_reason, status }, "subscription": { plan, tier, status, seats, mrr_cents, started_at, trial_ends_at, canceled_at } | null, "billing": { invoices_total: number, paid_cents: number, refunded_cents: number, failed_count: number, recent: object[] }, "support": { open_count: number, resolved_count: number, avg_csat: number | null, recent: object[] }, "usage_90d": [ { feature: string, events: number, quantity: number } ] } Example: "Why is Kestrel Robotics unhappy?" -> find_customer, then this tool; the open tickets and failed invoices usually answer it. Error: returns an error naming the id if no such customer exists. |
| harbor_revenue_summaryA | Revenue broken down by month, plan, country or industry. group_by='month' reads collected revenue from paid invoices, net of refunds. The other groupings read current MRR from live subscriptions, which is a different question — one is history, the other is run-rate. Args:
Returns JSON for group_by='month': { "basis": "collected_revenue", "rows": [ { "month": "2026-07", "invoices": number, "gross_cents": number, "refunded_cents": number, "net_cents": number } ], "totals": { "gross_cents": number, "refunded_cents": number, "net_cents": number } } Returns JSON for the other groupings: { "basis": "current_mrr", "rows": [ { "": string, "customers": number, "mrr_cents": number, "arpa_cents": number } ], "totals": { "customers": number, "mrr_cents": number } } Examples:
|
| harbor_issue_refundA | Refund part or all of a paid invoice. Requires two calls. Call 1 — omit confirm_token. Returns a preview of exactly what would change, plus a token valid for 5 minutes. Call 2 — pass that token with identical arguments. The refund is written. Args:
Returns for the preview call: { "stage": "preview", "confirm_token": string, "expires_in_seconds": number, "invoice": { id, customer_id, amount_cents, already_refunded_cents, status }, "would_refund_cents": number, "resulting_refunded_cents": number } Returns for the execute call: { "stage": "executed", "invoice_id": string, "refunded_cents": number, "total_refunded_cents": number } Refuses when: the invoice does not exist, is not paid, or the refund would exceed the amount actually charged. |
| harbor_extend_trialA | Push a trialing subscription's end date out by a number of days. Requires two calls. Call 1 — omit confirm_token to get a preview and a token. Call 2 — pass the token with identical arguments to apply it. Args:
Returns for preview: { "stage": "preview", "confirm_token": string, "expires_in_seconds": number, "current_trial_ends_at": string, "new_trial_ends_at": string } Returns for execute: { "stage": "executed", "customer_id": string, "new_trial_ends_at": string } Refuses when the customer has no subscription, or the subscription is not in 'trialing' status — you cannot start a new trial for a paying or churned account. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/aayushsinghm16/harbor-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server