@mindstone-engineering/mcp-server-freshdesk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,97 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Mindstone Engineering
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ **Licensor**: Mindstone Engineering
18
+
19
+ ### The Software
20
+
21
+ The "Software" is each version of the software that we make available under
22
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
23
+ Conditions with the Software.
24
+
25
+ **Software**: Freshdesk MCP Server
26
+
27
+ ### License Grant
28
+
29
+ Subject to your compliance with this License Grant and the Patents,
30
+ Redistribution and Trademark clauses below, we hereby grant you the right to
31
+ use, copy, modify, create derivative works, publicly perform, publicly display
32
+ and redistribute the Software for any Permitted Purpose identified below.
33
+
34
+ ### Permitted Purpose
35
+
36
+ A Permitted Purpose is any purpose other than a Competing Use. A "Competing
37
+ Use" means making the Software available to third parties as a commercial
38
+ hosted service that directly competes with any product or service provided by
39
+ the Licensor.
40
+
41
+ ### Patents
42
+
43
+ To the extent your use for a Permitted Purpose would necessarily infringe our
44
+ patents, the license grant above includes a license under our patents. If you
45
+ make a claim against any party that the Software infringes or contributes to
46
+ the infringement of any patent, then your patent license to the Software ends
47
+ immediately.
48
+
49
+ ### Redistribution
50
+
51
+ The Terms and Conditions apply to all copies, modifications and derivatives of
52
+ the Software.
53
+
54
+ If you redistribute any copies, modifications or derivatives of the Software,
55
+ you must include a copy of or a link to these Terms and Conditions and not
56
+ remove any copyright notices provided in or with the Software.
57
+
58
+ ### Disclaimer
59
+
60
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
61
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
62
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
63
+
64
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
65
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, OF
66
+ ANY CHARACTER INCLUDING DAMAGES FOR LOSS OF GOODWILL, LOST PROFITS, LOST SALES
67
+ OR BUSINESS, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, LOST CONTENT,
68
+ DATA OR DATA USE, BREACH OF DUTY OF GOOD FAITH, OR ANY AND ALL OTHER DAMAGES
69
+ OR LOSSES OF ANY KIND OR NATURE WHATSOEVER (WHETHER DIRECT, INDIRECT, SPECIAL,
70
+ COLLATERAL, INCIDENTAL, CONSEQUENTIAL OR OTHERWISE) ARISING OUT OF OR IN
71
+ CONNECTION WITH THE SOFTWARE OR THIS LICENSE, EVEN IF SUCH PARTY SHALL HAVE
72
+ BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES.
73
+
74
+ ### Trademark
75
+
76
+ Except for displaying the License Details and identifying us as the origin of
77
+ the Software, you have no right under these Terms and Conditions to use our
78
+ trademarks, trade names, service marks or product names.
79
+
80
+ ## Change Date
81
+
82
+ Four years from the date the Software is made available under these Terms and
83
+ Conditions: **2030-04-08**
84
+
85
+ ## Change License
86
+
87
+ MIT License
88
+
89
+ ## License Details
90
+
91
+ | Parameter | Value |
92
+ |---|---|
93
+ | Licensor | Mindstone Engineering |
94
+ | Software | Freshdesk MCP Server |
95
+ | Use Limitation | Competing Use |
96
+ | Change Date | 2030-04-08 |
97
+ | Change License | MIT |
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @mindstone-engineering/mcp-server-freshdesk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@mindstone-engineering/mcp-server-freshdesk.svg)](https://www.npmjs.com/package/@mindstone-engineering/mcp-server-freshdesk)
4
+ [![License: FSL-1.1-MIT](https://img.shields.io/badge/License-FSL--1.1--MIT-blue.svg)](./LICENSE)
5
+
6
+ Freshdesk Support MCP server for Model Context Protocol hosts. Manage helpdesk tickets, search and filter support requests, reply to customers, add internal notes, and configure Freshdesk accounts — all through a standardised MCP interface.
7
+
8
+ ## Requirements
9
+
10
+ - Node.js 20+
11
+ - npm
12
+
13
+ ## Quick Start
14
+
15
+ ### Install & build
16
+
17
+ ```bash
18
+ cd <path-to-repo>/connectors/freshdesk
19
+ npm install
20
+ npm run build
21
+ ```
22
+
23
+ ### npx (once published)
24
+
25
+ ```bash
26
+ npx -y @mindstone-engineering/mcp-server-freshdesk
27
+ ```
28
+
29
+ ### Local
30
+
31
+ ```bash
32
+ node dist/index.js
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ ### Environment variables
38
+
39
+ - `FRESHDESK_CONFIG_PATH` — path to the config directory that stores account credentials (defaults to `~/.mcp/freshdesk`)
40
+ - `MCP_HOST_BRIDGE_STATE` — optional path to a host bridge state file used for credential management
41
+ - `MINDSTONE_REBEL_BRIDGE_STATE` — backwards-compatible alias for `MCP_HOST_BRIDGE_STATE`
42
+
43
+ ## Host configuration examples
44
+
45
+ ### Claude Desktop / Cursor
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "Freshdesk": {
51
+ "command": "npx",
52
+ "args": ["-y", "@mindstone-engineering/mcp-server-freshdesk"],
53
+ "env": {
54
+ "FRESHDESK_CONFIG_PATH": "~/.mcp/freshdesk"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### Local development (no npm publish needed)
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "Freshdesk": {
67
+ "command": "node",
68
+ "args": ["<path-to-repo>/connectors/freshdesk/dist/index.js"],
69
+ "env": {
70
+ "FRESHDESK_CONFIG_PATH": "~/.mcp/freshdesk"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Tools (11)
78
+
79
+ ### Account management
80
+ - `configure_freshdesk` — Connect a Freshdesk account using subdomain and API key
81
+ - `list_freshdesk_accounts` — List connected Freshdesk accounts with agent emails
82
+ - `remove_freshdesk_account` — Disconnect a Freshdesk account
83
+
84
+ ### Tickets
85
+ - `list_freshdesk_tickets` — List tickets using predefined filters
86
+ - `get_freshdesk_ticket` — Get a single ticket by ID with optional conversations
87
+ - `search_freshdesk_tickets` — Search tickets using Freshdesk query syntax
88
+ - `create_freshdesk_ticket` — Create a new ticket
89
+ - `update_freshdesk_ticket` — Update ticket fields, status, or assignee
90
+ - `reply_to_freshdesk_ticket` — Add a public reply to a ticket
91
+ - `add_freshdesk_note` — Add a private or public note to a ticket
92
+
93
+ ### Discovery
94
+ - `list_freshdesk_ticket_fields` — List all ticket fields including custom fields
95
+
96
+ ## Licence
97
+
98
+ [FSL-1.1-MIT](./LICENSE) — Functional Source License, Version 1.1, with MIT future licence. The software converts to MIT licence on 2030-04-08.
package/dist/bridge.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare const BRIDGE_STATE_PATH: string;
5
5
  /**
6
6
  * Send a request to the host app bridge.
7
7
  *
8
- * The bridge is an HTTP server running inside the host app (e.g. Rebel)
8
+ * The bridge is an HTTP server running inside the host app (e.g. the host application)
9
9
  * that handles credential management and other cross-process operations.
10
10
  */
11
11
  export declare const bridgeRequest: (urlPath: string, body: Record<string, unknown>) => Promise<{
package/dist/bridge.js CHANGED
@@ -18,7 +18,7 @@ const loadBridgeState = () => {
18
18
  /**
19
19
  * Send a request to the host app bridge.
20
20
  *
21
- * The bridge is an HTTP server running inside the host app (e.g. Rebel)
21
+ * The bridge is an HTTP server running inside the host app (e.g. the host application)
22
22
  * that handles credential management and other cross-process operations.
23
23
  */
24
24
  export const bridgeRequest = async (urlPath, body) => {
package/dist/client.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * Base URL: https://{domain}.freshdesk.com/api/v2
9
9
  */
10
10
  import { FreshdeskError, REQUEST_TIMEOUT_MS } from './types.js';
11
+ import { validateSubdomain } from './utils.js';
11
12
  /**
12
13
  * Make an authenticated request to the Freshdesk API.
13
14
  *
@@ -18,6 +19,8 @@ import { FreshdeskError, REQUEST_TIMEOUT_MS } from './types.js';
18
19
  * @returns Parsed JSON response
19
20
  */
20
21
  export async function freshdeskFetch(domain, apiKey, endpoint, options = {}) {
22
+ // Defence-in-depth: validate domain before URL construction
23
+ validateSubdomain(domain);
21
24
  const { params, ...fetchOptions } = options;
22
25
  // Build URL with query params
23
26
  let url = `https://${domain}.freshdesk.com/api/v2${endpoint}`;
@@ -63,7 +66,7 @@ export async function freshdeskFetch(domain, apiKey, endpoint, options = {}) {
63
66
  }
64
67
  // Handle auth errors
65
68
  if (response.status === 401) {
66
- throw new FreshdeskError('Authentication failed', 'AUTH_FAILED', 'API key is invalid or revoked. Check your Freshdesk API key, or reconnect in Mindstone settings.');
69
+ throw new FreshdeskError('Authentication failed', 'AUTH_FAILED', 'API key is invalid or revoked. Check your Freshdesk API key in your MCP host\'s settings.');
67
70
  }
68
71
  // Handle forbidden
69
72
  if (response.status === 403) {
@@ -82,7 +85,7 @@ export async function freshdeskFetch(domain, apiKey, endpoint, options = {}) {
82
85
  : response.status >= 500
83
86
  ? 'Freshdesk server error - try again later'
84
87
  : 'Request failed';
85
- throw new FreshdeskError(`Freshdesk API error (${response.status}): ${statusMessage}`, 'API_ERROR', 'Check the request parameters and try again. If the problem persists, reconnect your Freshdesk account in Mindstone settings.');
88
+ throw new FreshdeskError(`Freshdesk API error (${response.status}): ${statusMessage}`, 'API_ERROR', 'Check the request parameters and try again. If the problem persists, reconnect your Freshdesk account in your MCP host\'s settings.');
86
89
  }
87
90
  // Handle 204 No Content
88
91
  if (response.status === 204) {
package/dist/index.d.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * Environment variables:
9
9
  * - FRESHDESK_CONFIG_PATH: Path to config directory containing accounts.json
10
- * - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (optional)
11
- * - MINDSTONE_REBEL_BRIDGE_STATE: Legacy bridge state path (optional)
10
+ * - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (primary, optional)
11
+ * - MINDSTONE_REBEL_BRIDGE_STATE: Legacy/deprecated bridge state path (optional)
12
12
  */
13
13
  export {};
14
14
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * Environment variables:
9
9
  * - FRESHDESK_CONFIG_PATH: Path to config directory containing accounts.json
10
- * - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (optional)
11
- * - MINDSTONE_REBEL_BRIDGE_STATE: Legacy bridge state path (optional)
10
+ * - MCP_HOST_BRIDGE_STATE: Path to host app bridge state file (primary, optional)
11
+ * - MINDSTONE_REBEL_BRIDGE_STATE: Legacy/deprecated bridge state path (optional)
12
12
  */
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { createServer } from './server.js';
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { loadAccounts, getAccountsConfig, removeAccount, upsertAccount } from '../auth.js';
3
3
  import { bridgeRequest, BRIDGE_STATE_PATH } from '../bridge.js';
4
4
  import { FreshdeskError } from '../types.js';
5
- import { withErrorHandling } from '../utils.js';
5
+ import { withErrorHandling, validateSubdomain } from '../utils.js';
6
6
  export function registerConfigureTools(server) {
7
7
  // ── configure_freshdesk ─────────────────────────────────────────
8
8
  server.registerTool('configure_freshdesk', {
@@ -22,6 +22,8 @@ export function registerConfigureTools(server) {
22
22
  }, withErrorHandling(async (args) => {
23
23
  const domain = args.domain.trim();
24
24
  const apiKey = args.api_key.trim();
25
+ // Validate subdomain before any storage or network call
26
+ validateSubdomain(domain);
25
27
  // If bridge is available, persist via bridge
26
28
  if (BRIDGE_STATE_PATH) {
27
29
  try {
@@ -71,7 +73,7 @@ export function registerConfigureTools(server) {
71
73
  return JSON.stringify({
72
74
  ok: true,
73
75
  accounts: [],
74
- message: 'No Freshdesk accounts connected. Use configure_freshdesk or go to Mindstone Settings > Integrations > Freshdesk to connect.',
76
+ message: 'No Freshdesk accounts connected. Use configure_freshdesk to connect your account.',
75
77
  });
76
78
  }
77
79
  const accountList = config.accounts.map((account) => ({
@@ -21,7 +21,7 @@ export function registerFieldTools(server) {
21
21
  return JSON.stringify({
22
22
  ok: false,
23
23
  error: 'No Freshdesk account connected',
24
- resolution: 'Use configure_freshdesk or go to Mindstone Settings > Integrations > Freshdesk to connect your account.',
24
+ resolution: 'Use configure_freshdesk to connect your account.',
25
25
  });
26
26
  }
27
27
  const fields = await freshdeskFetch(account.domain, account.apiKey, '/admin/ticket_fields');
@@ -8,7 +8,7 @@ function noAccountError() {
8
8
  return JSON.stringify({
9
9
  ok: false,
10
10
  error: 'No Freshdesk account connected',
11
- resolution: 'Use configure_freshdesk or go to Mindstone Settings > Integrations > Freshdesk to connect your account.',
11
+ resolution: 'Use configure_freshdesk to connect your account.',
12
12
  });
13
13
  }
14
14
  export function registerTicketTools(server) {
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Validates that a Freshdesk subdomain is a safe, single-label hostname component.
4
+ *
5
+ * Rejects any domain containing `/`, `@`, `?`, `#`, `.`, `%`, whitespace, or
6
+ * other characters that could redirect API requests to an attacker-controlled host.
7
+ *
8
+ * @throws {FreshdeskError} with code INVALID_SUBDOMAIN if validation fails
9
+ */
10
+ export declare function validateSubdomain(domain: string): void;
2
11
  type ToolHandler<T> = (args: T, extra: unknown) => Promise<CallToolResult>;
3
12
  /**
4
13
  * Wraps a tool handler with standard error handling.
package/dist/utils.js CHANGED
@@ -1,4 +1,27 @@
1
1
  import { FreshdeskError } from './types.js';
2
+ /**
3
+ * Strict subdomain regex: only lowercase alphanumerics and hyphens,
4
+ * must start and end with an alphanumeric character.
5
+ * Prevents API key exfiltration via URL manipulation.
6
+ */
7
+ const SUBDOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
8
+ /**
9
+ * Validates that a Freshdesk subdomain is a safe, single-label hostname component.
10
+ *
11
+ * Rejects any domain containing `/`, `@`, `?`, `#`, `.`, `%`, whitespace, or
12
+ * other characters that could redirect API requests to an attacker-controlled host.
13
+ *
14
+ * @throws {FreshdeskError} with code INVALID_SUBDOMAIN if validation fails
15
+ */
16
+ export function validateSubdomain(domain) {
17
+ if (!domain || !domain.trim()) {
18
+ throw new FreshdeskError('Freshdesk subdomain cannot be empty', 'INVALID_SUBDOMAIN', 'Provide a valid Freshdesk subdomain (e.g. "acme" for acme.freshdesk.com).');
19
+ }
20
+ const trimmed = domain.trim();
21
+ if (!SUBDOMAIN_RE.test(trimmed)) {
22
+ throw new FreshdeskError(`Invalid Freshdesk subdomain: "${trimmed}". Only lowercase alphanumeric characters and hyphens are allowed.`, 'INVALID_SUBDOMAIN', 'Provide just the subdomain part (e.g. "acme" for acme.freshdesk.com). Do not include the full URL, dots, or special characters.');
23
+ }
24
+ }
2
25
  /**
3
26
  * Wraps a tool handler with standard error handling.
4
27
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstone-engineering/mcp-server-freshdesk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Freshdesk Support MCP server for Model Context Protocol hosts",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "type": "module",