supporthero-mcp-server 1.0.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/README.md +81 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/inputs.d.ts +48 -0
- package/dist/schemas/inputs.d.ts.map +1 -0
- package/dist/schemas/inputs.js +60 -0
- package/dist/schemas/inputs.js.map +1 -0
- package/dist/services/client.d.ts +25 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +72 -0
- package/dist/services/client.js.map +1 -0
- package/dist/services/formatting.d.ts +13 -0
- package/dist/services/formatting.d.ts.map +1 -0
- package/dist/services/formatting.js +68 -0
- package/dist/services/formatting.js.map +1 -0
- package/dist/tools/helpCenter.d.ts +7 -0
- package/dist/tools/helpCenter.d.ts.map +1 -0
- package/dist/tools/helpCenter.js +265 -0
- package/dist/tools/helpCenter.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +20 -0
- package/src/constants.ts +5 -0
- package/src/index.ts +49 -0
- package/src/schemas/inputs.ts +85 -0
- package/src/services/client.ts +96 -0
- package/src/services/formatting.ts +74 -0
- package/src/tools/helpCenter.ts +320 -0
- package/src/types.ts +50 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# SupportHero MCP Server
|
|
2
|
+
|
|
3
|
+
A read-only MCP server for the SupportHero Help Center API. Enables Claude to search, browse, and read your help center articles directly in conversation.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `supporthero_list_categories` | List top-level or sub-categories |
|
|
10
|
+
| `supporthero_get_category` | Get details of a specific category |
|
|
11
|
+
| `supporthero_list_articles` | List articles in a category (summaries only) |
|
|
12
|
+
| `supporthero_get_article` | Get full article content by ID |
|
|
13
|
+
| `supporthero_search_articles` | Full-text search across all articles |
|
|
14
|
+
|
|
15
|
+
All tools are read-only. No data is modified.
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
### 1. Install dependencies
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd supporthero-mcp-server
|
|
23
|
+
npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Build
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Find your API key
|
|
33
|
+
|
|
34
|
+
Go to your SupportHero account > Settings (scroll to bottom of page). Copy the API key.
|
|
35
|
+
|
|
36
|
+
### 4. Configure Claude Desktop
|
|
37
|
+
|
|
38
|
+
Add this to your Claude Desktop config file:
|
|
39
|
+
|
|
40
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
41
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"supporthero": {
|
|
47
|
+
"command": "node",
|
|
48
|
+
"args": ["/absolute/path/to/supporthero-mcp-server/dist/index.js"],
|
|
49
|
+
"env": {
|
|
50
|
+
"SUPPORTHERO_DOMAIN": "https://yourcompany.supporthero.io",
|
|
51
|
+
"SUPPORTHERO_API_KEY": "your-api-key-here"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Replace:
|
|
59
|
+
- `/absolute/path/to/` with the actual path where you cloned this project
|
|
60
|
+
- `yourcompany` with your SupportHero subdomain
|
|
61
|
+
- `your-api-key-here` with your actual API key
|
|
62
|
+
|
|
63
|
+
### 5. Restart Claude Desktop
|
|
64
|
+
|
|
65
|
+
The SupportHero tools should now appear in Claude's tool list.
|
|
66
|
+
|
|
67
|
+
## Usage Examples
|
|
68
|
+
|
|
69
|
+
Once connected, you can ask Claude things like:
|
|
70
|
+
|
|
71
|
+
- "What help center articles do we have about cancellations?"
|
|
72
|
+
- "Show me the full content of our booking policy article"
|
|
73
|
+
- "List all categories in the help center"
|
|
74
|
+
- "Search our help center for instructor qualifications"
|
|
75
|
+
|
|
76
|
+
## Environment Variables
|
|
77
|
+
|
|
78
|
+
| Variable | Required | Description |
|
|
79
|
+
|----------|----------|-------------|
|
|
80
|
+
| `SUPPORTHERO_DOMAIN` | Yes | Your Help Center URL (e.g. `https://propel.supporthero.io`) |
|
|
81
|
+
| `SUPPORTHERO_API_KEY` | Yes | API key from SupportHero Settings page |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,WAAW,OAAO,CAAC;AAChC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,eAAe,QAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAChC,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { SupportHeroClient } from "./services/client.js";
|
|
4
|
+
import { registerTools } from "./tools/helpCenter.js";
|
|
5
|
+
// ── Configuration ───────────────────────────────────────────
|
|
6
|
+
const SUPPORTHERO_DOMAIN = process.env.SUPPORTHERO_DOMAIN;
|
|
7
|
+
const SUPPORTHERO_API_KEY = process.env.SUPPORTHERO_API_KEY;
|
|
8
|
+
if (!SUPPORTHERO_DOMAIN) {
|
|
9
|
+
console.error("Missing SUPPORTHERO_DOMAIN env var. " +
|
|
10
|
+
"Set it to your Help Center URL, e.g. https://yourcompany.supporthero.io");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (!SUPPORTHERO_API_KEY) {
|
|
14
|
+
console.error("Missing SUPPORTHERO_API_KEY env var. " +
|
|
15
|
+
"Find your API key in SupportHero > Settings (bottom of page).");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// ── Server Setup ────────────────────────────────────────────
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: "supporthero-mcp-server",
|
|
21
|
+
version: "1.0.0",
|
|
22
|
+
});
|
|
23
|
+
const client = new SupportHeroClient(SUPPORTHERO_DOMAIN, SUPPORTHERO_API_KEY);
|
|
24
|
+
registerTools(server, client);
|
|
25
|
+
// ── Transport ───────────────────────────────────────────────
|
|
26
|
+
async function main() {
|
|
27
|
+
const transport = new StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
console.error("SupportHero MCP server running on stdio");
|
|
30
|
+
}
|
|
31
|
+
main().catch((error) => {
|
|
32
|
+
console.error("Fatal error:", error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,+DAA+D;AAE/D,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAC1D,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;AAE5D,IAAI,CAAC,kBAAkB,EAAE,CAAC;IACxB,OAAO,CAAC,KAAK,CACX,sCAAsC;QACtC,yEAAyE,CAC1E,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;IACzB,OAAO,CAAC,KAAK,CACX,uCAAuC;QACvC,+DAA+D,CAChE,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,+DAA+D;AAE/D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,wBAAwB;IAC9B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;AAE9E,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9B,+DAA+D;AAE/D,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC3D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ListCategoriesInputSchema: z.ZodObject<{
|
|
3
|
+
parent_category_id: z.ZodOptional<z.ZodString>;
|
|
4
|
+
}, "strict", z.ZodTypeAny, {
|
|
5
|
+
parent_category_id?: string | undefined;
|
|
6
|
+
}, {
|
|
7
|
+
parent_category_id?: string | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
export type ListCategoriesInput = z.infer<typeof ListCategoriesInputSchema>;
|
|
10
|
+
export declare const GetCategoryInputSchema: z.ZodObject<{
|
|
11
|
+
category_id: z.ZodString;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
category_id: string;
|
|
14
|
+
}, {
|
|
15
|
+
category_id: string;
|
|
16
|
+
}>;
|
|
17
|
+
export type GetCategoryInput = z.infer<typeof GetCategoryInputSchema>;
|
|
18
|
+
export declare const ListArticlesInputSchema: z.ZodObject<{
|
|
19
|
+
category_id: z.ZodString;
|
|
20
|
+
}, "strict", z.ZodTypeAny, {
|
|
21
|
+
category_id: string;
|
|
22
|
+
}, {
|
|
23
|
+
category_id: string;
|
|
24
|
+
}>;
|
|
25
|
+
export type ListArticlesInput = z.infer<typeof ListArticlesInputSchema>;
|
|
26
|
+
export declare const GetArticleInputSchema: z.ZodObject<{
|
|
27
|
+
article_id: z.ZodString;
|
|
28
|
+
}, "strict", z.ZodTypeAny, {
|
|
29
|
+
article_id: string;
|
|
30
|
+
}, {
|
|
31
|
+
article_id: string;
|
|
32
|
+
}>;
|
|
33
|
+
export type GetArticleInput = z.infer<typeof GetArticleInputSchema>;
|
|
34
|
+
export declare const SearchArticlesInputSchema: z.ZodObject<{
|
|
35
|
+
search_text: z.ZodString;
|
|
36
|
+
max: z.ZodDefault<z.ZodNumber>;
|
|
37
|
+
offset: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, "strict", z.ZodTypeAny, {
|
|
39
|
+
max: number;
|
|
40
|
+
search_text: string;
|
|
41
|
+
offset?: string | undefined;
|
|
42
|
+
}, {
|
|
43
|
+
search_text: string;
|
|
44
|
+
max?: number | undefined;
|
|
45
|
+
offset?: string | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export type SearchArticlesInput = z.infer<typeof SearchArticlesInputSchema>;
|
|
48
|
+
//# sourceMappingURL=inputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../src/schemas/inputs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,yBAAyB;;;;;;EAU3B,CAAC;AAEZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,sBAAsB;;;;;;EAOxB,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,uBAAuB;;;;;;EAUzB,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;EAOvB,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIpE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;EAuB3B,CAAC;AAEZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MAX } from "../constants.js";
|
|
3
|
+
// ── Categories ──────────────────────────────────────────────
|
|
4
|
+
export const ListCategoriesInputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
parent_category_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("If provided, returns sub-categories of this category. " +
|
|
10
|
+
"If omitted, returns all top-level categories."),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
export const GetCategoryInputSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
category_id: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1, "category_id is required")
|
|
18
|
+
.describe("The ID of the category to retrieve."),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
// ── Articles ────────────────────────────────────────────────
|
|
22
|
+
export const ListArticlesInputSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
category_id: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1, "category_id is required")
|
|
27
|
+
.describe("The ID of the category whose articles to list. " +
|
|
28
|
+
"Use supporthero_list_categories first to find category IDs."),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
export const GetArticleInputSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
article_id: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1, "article_id is required")
|
|
36
|
+
.describe("The ID of the article to retrieve. Returns full content."),
|
|
37
|
+
})
|
|
38
|
+
.strict();
|
|
39
|
+
// ── Search ──────────────────────────────────────────────────
|
|
40
|
+
export const SearchArticlesInputSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
search_text: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1, "search_text is required")
|
|
45
|
+
.describe("The search query. Matches against article titles and content."),
|
|
46
|
+
max: z
|
|
47
|
+
.number()
|
|
48
|
+
.int()
|
|
49
|
+
.min(1)
|
|
50
|
+
.max(MAX_SEARCH_RESULTS)
|
|
51
|
+
.default(DEFAULT_SEARCH_MAX)
|
|
52
|
+
.describe(`Maximum number of results to return per page (1 to ${MAX_SEARCH_RESULTS}, default ${DEFAULT_SEARCH_MAX}).`),
|
|
53
|
+
offset: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Pagination cursor from a previous search response. " +
|
|
57
|
+
"Pass the offset value from the prior result to get the next page."),
|
|
58
|
+
})
|
|
59
|
+
.strict();
|
|
60
|
+
//# sourceMappingURL=inputs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputs.js","sourceRoot":"","sources":["../../src/schemas/inputs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,+DAA+D;AAE/D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACvC,MAAM,CAAC;IACN,kBAAkB,EAAE,CAAC;SAClB,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,wDAAwD;QACxD,+CAA+C,CAChD;CACJ,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KACpC,MAAM,CAAC;IACN,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;SACjC,QAAQ,CAAC,qCAAqC,CAAC;CACnD,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,+DAA+D;AAE/D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;SACjC,QAAQ,CACP,iDAAiD;QACjD,6DAA6D,CAC9D;CACJ,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC;KACnC,MAAM,CAAC;IACN,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,wBAAwB,CAAC;SAChC,QAAQ,CAAC,0DAA0D,CAAC;CACxE,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ,+DAA+D;AAE/D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC;KACvC,MAAM,CAAC;IACN,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;SACjC,QAAQ,CAAC,+DAA+D,CAAC;IAC5E,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,kBAAkB,CAAC;SACvB,OAAO,CAAC,kBAAkB,CAAC;SAC3B,QAAQ,CACP,sDAAsD,kBAAkB,aAAa,kBAAkB,IAAI,CAC5G;IACH,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,qDAAqD;QACrD,mEAAmE,CACpE;CACJ,CAAC;KACD,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SHCategory, SHArticle, SHSearchResponse } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* SupportHero API client.
|
|
4
|
+
* All methods are read-only GET requests against the Help Center API.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SupportHeroClient {
|
|
7
|
+
private baseUrl;
|
|
8
|
+
private apiKey;
|
|
9
|
+
constructor(baseUrl: string, apiKey: string);
|
|
10
|
+
/**
|
|
11
|
+
* Core fetch wrapper. Appends apiKey and handles errors uniformly.
|
|
12
|
+
*/
|
|
13
|
+
private request;
|
|
14
|
+
/** List top-level categories, or sub-categories of a given parent. */
|
|
15
|
+
listCategories(parentCategoryId?: string): Promise<SHCategory[]>;
|
|
16
|
+
/** Get a single category by ID. */
|
|
17
|
+
getCategory(categoryId: string): Promise<SHCategory>;
|
|
18
|
+
/** List all articles in a given category. */
|
|
19
|
+
listArticles(categoryId: string): Promise<SHArticle[]>;
|
|
20
|
+
/** Get a single article by ID. */
|
|
21
|
+
getArticle(articleId: string): Promise<SHArticle>;
|
|
22
|
+
/** Full-text search across all articles. Returns paginated results. */
|
|
23
|
+
searchArticles(searchText: string, max?: number, offset?: string): Promise<SHSearchResponse>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EAEV,SAAS,EAET,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM3C;;OAEG;YACW,OAAO;IA0BrB,sEAAsE;IAChE,cAAc,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAStE,mCAAmC;IAC7B,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAM1D,6CAA6C;IACvC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAO5D,kCAAkC;IAC5B,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAMvD,uEAAuE;IACjE,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,CAAC;CAM7B"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { API_VERSION } from "../constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* SupportHero API client.
|
|
4
|
+
* All methods are read-only GET requests against the Help Center API.
|
|
5
|
+
*/
|
|
6
|
+
export class SupportHeroClient {
|
|
7
|
+
baseUrl;
|
|
8
|
+
apiKey;
|
|
9
|
+
constructor(baseUrl, apiKey) {
|
|
10
|
+
// Normalize: strip trailing slash
|
|
11
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
12
|
+
this.apiKey = apiKey;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Core fetch wrapper. Appends apiKey and handles errors uniformly.
|
|
16
|
+
*/
|
|
17
|
+
async request(path, params = {}) {
|
|
18
|
+
const url = new URL(`/api/${API_VERSION}/${path}`, this.baseUrl);
|
|
19
|
+
url.searchParams.set("apiKey", this.apiKey);
|
|
20
|
+
for (const [key, value] of Object.entries(params)) {
|
|
21
|
+
if (value !== undefined && value !== "") {
|
|
22
|
+
url.searchParams.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const response = await fetch(url.toString(), {
|
|
26
|
+
method: "GET",
|
|
27
|
+
headers: { "Accept": "application/json" },
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const body = await response.text().catch(() => "");
|
|
31
|
+
throw new Error(`SupportHero API error ${response.status}: ${response.statusText}. ${body}`);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
// ── Categories ──────────────────────────────────────────────
|
|
36
|
+
/** List top-level categories, or sub-categories of a given parent. */
|
|
37
|
+
async listCategories(parentCategoryId) {
|
|
38
|
+
const params = {};
|
|
39
|
+
if (parentCategoryId) {
|
|
40
|
+
params.categoryId = parentCategoryId;
|
|
41
|
+
}
|
|
42
|
+
const data = await this.request("category", params);
|
|
43
|
+
return data.categories;
|
|
44
|
+
}
|
|
45
|
+
/** Get a single category by ID. */
|
|
46
|
+
async getCategory(categoryId) {
|
|
47
|
+
return this.request(`category/${categoryId}`);
|
|
48
|
+
}
|
|
49
|
+
// ── Articles ────────────────────────────────────────────────
|
|
50
|
+
/** List all articles in a given category. */
|
|
51
|
+
async listArticles(categoryId) {
|
|
52
|
+
const data = await this.request("article", {
|
|
53
|
+
categoryId,
|
|
54
|
+
});
|
|
55
|
+
return data.articles;
|
|
56
|
+
}
|
|
57
|
+
/** Get a single article by ID. */
|
|
58
|
+
async getArticle(articleId) {
|
|
59
|
+
return this.request(`article/${articleId}`);
|
|
60
|
+
}
|
|
61
|
+
// ── Search ──────────────────────────────────────────────────
|
|
62
|
+
/** Full-text search across all articles. Returns paginated results. */
|
|
63
|
+
async searchArticles(searchText, max, offset) {
|
|
64
|
+
const params = { searchText };
|
|
65
|
+
if (max !== undefined)
|
|
66
|
+
params.max = String(max);
|
|
67
|
+
if (offset)
|
|
68
|
+
params.offset = offset;
|
|
69
|
+
return this.request("search", params);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SHCategory, SHArticle } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Strip HTML tags from article body to produce plain text.
|
|
4
|
+
* Keeps it simple since we just need readable content for the LLM.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stripHtml(html: string): string;
|
|
7
|
+
/** Format a single category for display. */
|
|
8
|
+
export declare function formatCategory(cat: SHCategory): string;
|
|
9
|
+
/** Format a single article for display. Optionally include body content. */
|
|
10
|
+
export declare function formatArticle(article: SHArticle, includeBody?: boolean): string;
|
|
11
|
+
/** Truncate text if it exceeds the character limit, with a warning. */
|
|
12
|
+
export declare function truncateIfNeeded(text: string): string;
|
|
13
|
+
//# sourceMappingURL=formatting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../../src/services/formatting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGzD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAe9C;AAED,4CAA4C;AAC5C,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAYtD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,GAAE,OAAe,GAAG,MAAM,CA0BtF;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CHARACTER_LIMIT } from "../constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Strip HTML tags from article body to produce plain text.
|
|
4
|
+
* Keeps it simple since we just need readable content for the LLM.
|
|
5
|
+
*/
|
|
6
|
+
export function stripHtml(html) {
|
|
7
|
+
return html
|
|
8
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
9
|
+
.replace(/<\/p>/gi, "\n\n")
|
|
10
|
+
.replace(/<\/li>/gi, "\n")
|
|
11
|
+
.replace(/<\/h[1-6]>/gi, "\n\n")
|
|
12
|
+
.replace(/<[^>]+>/g, "")
|
|
13
|
+
.replace(/&/g, "&")
|
|
14
|
+
.replace(/</g, "<")
|
|
15
|
+
.replace(/>/g, ">")
|
|
16
|
+
.replace(/"/g, '"')
|
|
17
|
+
.replace(/'/g, "'")
|
|
18
|
+
.replace(/ /g, " ")
|
|
19
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
20
|
+
.trim();
|
|
21
|
+
}
|
|
22
|
+
/** Format a single category for display. */
|
|
23
|
+
export function formatCategory(cat) {
|
|
24
|
+
return [
|
|
25
|
+
`Category: ${cat.name} (ID: ${cat.id})`,
|
|
26
|
+
` Level: ${cat.level}`,
|
|
27
|
+
` Published articles: ${cat.articlePublishedCount}`,
|
|
28
|
+
` Total articles: ${cat.articleCount}`,
|
|
29
|
+
` Sub-categories: ${cat.categoriesCount}`,
|
|
30
|
+
cat.parentId !== "0" ? ` Parent ID: ${cat.parentId}` : null,
|
|
31
|
+
` Slug: ${cat.slug}`,
|
|
32
|
+
]
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join("\n");
|
|
35
|
+
}
|
|
36
|
+
/** Format a single article for display. Optionally include body content. */
|
|
37
|
+
export function formatArticle(article, includeBody = false) {
|
|
38
|
+
const lines = [
|
|
39
|
+
`Title: ${article.title} (ID: ${article.id})`,
|
|
40
|
+
` Category ID: ${article.categoryId}`,
|
|
41
|
+
` Type: ${article.type}`,
|
|
42
|
+
` Published: ${article.published ? "yes" : "no"}`,
|
|
43
|
+
` Created: ${article.dateCreated}`,
|
|
44
|
+
` Updated: ${article.lastUpdated}`,
|
|
45
|
+
];
|
|
46
|
+
if (article.keywords) {
|
|
47
|
+
lines.push(` Keywords: ${article.keywords}`);
|
|
48
|
+
}
|
|
49
|
+
if (article.slug) {
|
|
50
|
+
lines.push(` Slug: ${article.slug}`);
|
|
51
|
+
}
|
|
52
|
+
if (includeBody && article.description) {
|
|
53
|
+
const body = stripHtml(article.description);
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push("--- Content ---");
|
|
56
|
+
lines.push(body);
|
|
57
|
+
lines.push("--- End Content ---");
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
/** Truncate text if it exceeds the character limit, with a warning. */
|
|
62
|
+
export function truncateIfNeeded(text) {
|
|
63
|
+
if (text.length <= CHARACTER_LIMIT)
|
|
64
|
+
return text;
|
|
65
|
+
const truncated = text.slice(0, CHARACTER_LIMIT);
|
|
66
|
+
return truncated + "\n\n[Response truncated. Use more specific queries or pagination to get remaining results.]";
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=formatting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatting.js","sourceRoot":"","sources":["../../src/services/formatting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { SupportHeroClient } from "../services/client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register all SupportHero tools on the given MCP server.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerTools(server: McpServer, client: SupportHeroClient): void;
|
|
7
|
+
//# sourceMappingURL=helpCenter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpCenter.d.ts","sourceRoot":"","sources":["../../src/tools/helpCenter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAqB1D;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAsShF"}
|