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 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| 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