@frase/mcp-server 0.1.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +262 -0
  3. package/dist/api-client.d.ts +93 -0
  4. package/dist/api-client.d.ts.map +1 -0
  5. package/dist/api-client.js +213 -0
  6. package/dist/api-client.js.map +1 -0
  7. package/dist/cache.d.ts +52 -0
  8. package/dist/cache.d.ts.map +1 -0
  9. package/dist/cache.js +97 -0
  10. package/dist/cache.js.map +1 -0
  11. package/dist/config.d.ts +17 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +31 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/formatter.d.ts +47 -0
  16. package/dist/formatter.d.ts.map +1 -0
  17. package/dist/formatter.js +136 -0
  18. package/dist/formatter.js.map +1 -0
  19. package/dist/index.d.ts +32 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +292 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/prompts/competitor-analysis.d.ts +18 -0
  24. package/dist/prompts/competitor-analysis.d.ts.map +1 -0
  25. package/dist/prompts/competitor-analysis.js +71 -0
  26. package/dist/prompts/competitor-analysis.js.map +1 -0
  27. package/dist/prompts/content-audit.d.ts +18 -0
  28. package/dist/prompts/content-audit.d.ts.map +1 -0
  29. package/dist/prompts/content-audit.js +67 -0
  30. package/dist/prompts/content-audit.js.map +1 -0
  31. package/dist/prompts/create-seo-article.d.ts +19 -0
  32. package/dist/prompts/create-seo-article.d.ts.map +1 -0
  33. package/dist/prompts/create-seo-article.js +78 -0
  34. package/dist/prompts/create-seo-article.js.map +1 -0
  35. package/dist/prompts/index.d.ts +18 -0
  36. package/dist/prompts/index.d.ts.map +1 -0
  37. package/dist/prompts/index.js +52 -0
  38. package/dist/prompts/index.js.map +1 -0
  39. package/dist/prompts/keyword-research.d.ts +18 -0
  40. package/dist/prompts/keyword-research.d.ts.map +1 -0
  41. package/dist/prompts/keyword-research.js +72 -0
  42. package/dist/prompts/keyword-research.js.map +1 -0
  43. package/dist/prompts/optimize-content.d.ts +18 -0
  44. package/dist/prompts/optimize-content.d.ts.map +1 -0
  45. package/dist/prompts/optimize-content.js +59 -0
  46. package/dist/prompts/optimize-content.js.map +1 -0
  47. package/dist/resources/briefs.d.ts +26 -0
  48. package/dist/resources/briefs.d.ts.map +1 -0
  49. package/dist/resources/briefs.js +144 -0
  50. package/dist/resources/briefs.js.map +1 -0
  51. package/dist/resources/content.d.ts +26 -0
  52. package/dist/resources/content.d.ts.map +1 -0
  53. package/dist/resources/content.js +128 -0
  54. package/dist/resources/content.js.map +1 -0
  55. package/dist/resources/index.d.ts +32 -0
  56. package/dist/resources/index.d.ts.map +1 -0
  57. package/dist/resources/index.js +85 -0
  58. package/dist/resources/index.js.map +1 -0
  59. package/dist/resources/sites.d.ts +26 -0
  60. package/dist/resources/sites.d.ts.map +1 -0
  61. package/dist/resources/sites.js +108 -0
  62. package/dist/resources/sites.js.map +1 -0
  63. package/dist/tools/ai-visibility.d.ts +25 -0
  64. package/dist/tools/ai-visibility.d.ts.map +1 -0
  65. package/dist/tools/ai-visibility.js +537 -0
  66. package/dist/tools/ai-visibility.js.map +1 -0
  67. package/dist/tools/analytics.d.ts +17 -0
  68. package/dist/tools/analytics.d.ts.map +1 -0
  69. package/dist/tools/analytics.js +311 -0
  70. package/dist/tools/analytics.js.map +1 -0
  71. package/dist/tools/audits.d.ts +73 -0
  72. package/dist/tools/audits.d.ts.map +1 -0
  73. package/dist/tools/audits.js +345 -0
  74. package/dist/tools/audits.js.map +1 -0
  75. package/dist/tools/briefs.d.ts +63 -0
  76. package/dist/tools/briefs.d.ts.map +1 -0
  77. package/dist/tools/briefs.js +276 -0
  78. package/dist/tools/briefs.js.map +1 -0
  79. package/dist/tools/content.d.ts +51 -0
  80. package/dist/tools/content.d.ts.map +1 -0
  81. package/dist/tools/content.js +233 -0
  82. package/dist/tools/content.js.map +1 -0
  83. package/dist/tools/index.d.ts +29 -0
  84. package/dist/tools/index.d.ts.map +1 -0
  85. package/dist/tools/index.js +96 -0
  86. package/dist/tools/index.js.map +1 -0
  87. package/dist/tools/jobs.d.ts +22 -0
  88. package/dist/tools/jobs.d.ts.map +1 -0
  89. package/dist/tools/jobs.js +124 -0
  90. package/dist/tools/jobs.js.map +1 -0
  91. package/dist/tools/optimizations.d.ts +19 -0
  92. package/dist/tools/optimizations.d.ts.map +1 -0
  93. package/dist/tools/optimizations.js +339 -0
  94. package/dist/tools/optimizations.js.map +1 -0
  95. package/dist/tools/research.d.ts +41 -0
  96. package/dist/tools/research.d.ts.map +1 -0
  97. package/dist/tools/research.js +151 -0
  98. package/dist/tools/research.js.map +1 -0
  99. package/dist/tools/serp.d.ts +15 -0
  100. package/dist/tools/serp.d.ts.map +1 -0
  101. package/dist/tools/serp.js +267 -0
  102. package/dist/tools/serp.js.map +1 -0
  103. package/dist/tools/sites.d.ts +31 -0
  104. package/dist/tools/sites.d.ts.map +1 -0
  105. package/dist/tools/sites.js +83 -0
  106. package/dist/tools/sites.js.map +1 -0
  107. package/dist/tools/webhooks.d.ts +19 -0
  108. package/dist/tools/webhooks.d.ts.map +1 -0
  109. package/dist/tools/webhooks.js +350 -0
  110. package/dist/tools/webhooks.js.map +1 -0
  111. package/dist/types.d.ts +167 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +5 -0
  114. package/dist/types.js.map +1 -0
  115. package/package.json +67 -0
package/dist/cache.js ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Simple in-memory cache for GET responses
3
+ */
4
+ /**
5
+ * Cache TTL configuration (in milliseconds)
6
+ */
7
+ export const CACHE_TTL = {
8
+ lists: 60_000, // 1 minute for list endpoints
9
+ resources: 300_000, // 5 minutes for individual resources
10
+ };
11
+ /**
12
+ * Simple cache implementation
13
+ */
14
+ export class ResponseCache {
15
+ cache = new Map();
16
+ cleanupInterval = null;
17
+ constructor() {
18
+ // Run cleanup every minute
19
+ this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
20
+ }
21
+ /**
22
+ * Get a cached value
23
+ */
24
+ get(key) {
25
+ const entry = this.cache.get(key);
26
+ if (!entry) {
27
+ return undefined;
28
+ }
29
+ if (Date.now() > entry.expiresAt) {
30
+ this.cache.delete(key);
31
+ return undefined;
32
+ }
33
+ return entry.data;
34
+ }
35
+ /**
36
+ * Set a cached value
37
+ */
38
+ set(key, data, ttl) {
39
+ this.cache.set(key, {
40
+ data,
41
+ expiresAt: Date.now() + ttl,
42
+ });
43
+ }
44
+ /**
45
+ * Generate cache key for a request
46
+ */
47
+ static key(method, path, params) {
48
+ const paramStr = params ? JSON.stringify(params) : "";
49
+ return `${method}:${path}:${paramStr}`;
50
+ }
51
+ /**
52
+ * Invalidate cache entries matching a pattern
53
+ */
54
+ invalidate(pattern) {
55
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
56
+ for (const key of this.cache.keys()) {
57
+ if (regex.test(key)) {
58
+ this.cache.delete(key);
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Invalidate all cache entries for a resource type
64
+ * Called after write operations
65
+ */
66
+ invalidateResourceType(resourceType) {
67
+ this.invalidate(new RegExp(`:/${resourceType}`));
68
+ }
69
+ /**
70
+ * Clear all cached entries
71
+ */
72
+ clear() {
73
+ this.cache.clear();
74
+ }
75
+ /**
76
+ * Clean up expired entries
77
+ */
78
+ cleanup() {
79
+ const now = Date.now();
80
+ for (const [key, entry] of this.cache.entries()) {
81
+ if (now > entry.expiresAt) {
82
+ this.cache.delete(key);
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Stop the cleanup interval (for graceful shutdown)
88
+ */
89
+ destroy() {
90
+ if (this.cleanupInterval) {
91
+ clearInterval(this.cleanupInterval);
92
+ this.cleanupInterval = null;
93
+ }
94
+ this.cache.clear();
95
+ }
96
+ }
97
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration for the Frase MCP Server
3
+ */
4
+ export interface Config {
5
+ apiKey: string;
6
+ apiUrl: string;
7
+ debug: boolean;
8
+ }
9
+ /**
10
+ * Load configuration from environment variables
11
+ */
12
+ export declare function loadConfig(): Config;
13
+ /**
14
+ * Log debug message if debug mode is enabled
15
+ */
16
+ export declare function debugLog(config: Config, ...args: unknown[]): void;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAOD;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAenC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAIjE"}
package/dist/config.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Configuration for the Frase MCP Server
3
+ */
4
+ /**
5
+ * Default API URL
6
+ */
7
+ const DEFAULT_API_URL = "https://next.frase.io/api/v1";
8
+ /**
9
+ * Load configuration from environment variables
10
+ */
11
+ export function loadConfig() {
12
+ const apiKey = process.env.FRASE_API_KEY;
13
+ if (!apiKey) {
14
+ throw new Error("FRASE_API_KEY environment variable is required. " +
15
+ "Get your API key from https://app.frase.io/settings/api");
16
+ }
17
+ return {
18
+ apiKey,
19
+ apiUrl: process.env.FRASE_API_URL || DEFAULT_API_URL,
20
+ debug: process.env.FRASE_MCP_DEBUG === "true",
21
+ };
22
+ }
23
+ /**
24
+ * Log debug message if debug mode is enabled
25
+ */
26
+ export function debugLog(config, ...args) {
27
+ if (config.debug) {
28
+ console.error("[Frase MCP Debug]", ...args);
29
+ }
30
+ }
31
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH;;GAEG;AACH,MAAM,eAAe,GAAG,8BAA8B,CAAC;AAEvD;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAEzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,kDAAkD;YAChD,yDAAyD,CAC5D,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM;QACN,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,eAAe;QACpD,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,MAAM;KAC9C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,GAAG,IAAe;IACzD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Markdown response formatter for MCP tools
3
+ */
4
+ /**
5
+ * Format a date for display
6
+ */
7
+ export declare function formatDate(date: string | Date): string;
8
+ /**
9
+ * Format status with emoji
10
+ */
11
+ export declare function formatStatus(status: string): string;
12
+ /**
13
+ * Format a list of items as a markdown table
14
+ */
15
+ export declare function formatTable(headers: string[], rows: string[][], options?: {
16
+ align?: ("left" | "center" | "right")[];
17
+ }): string;
18
+ /**
19
+ * Format pagination info
20
+ */
21
+ export declare function formatPagination(pagination: {
22
+ page: number;
23
+ page_size: number;
24
+ total: number;
25
+ has_more: boolean;
26
+ }): string;
27
+ /**
28
+ * Format a success response
29
+ */
30
+ export declare function formatSuccess(title: string, content: string): string;
31
+ /**
32
+ * Format an error response
33
+ */
34
+ export declare function formatError(error: Error | string): string;
35
+ /**
36
+ * Format a key-value list
37
+ */
38
+ export declare function formatKeyValue(items: Record<string, string | number | boolean | null | undefined>): string;
39
+ /**
40
+ * Format word count
41
+ */
42
+ export declare function formatWordCount(count: number | null | undefined): string;
43
+ /**
44
+ * Truncate text with ellipsis
45
+ */
46
+ export declare function truncate(text: string, maxLength: number): string;
47
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAOtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA2BnD;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,EAAE,MAAM,EAAE,EAAE,EAChB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,EAAE,CAAA;CAAE,GACpD,MAAM,CAiCR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,CAYT;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAGzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAa1G;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAUxE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMhE"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Markdown response formatter for MCP tools
3
+ */
4
+ /**
5
+ * Format a date for display
6
+ */
7
+ export function formatDate(date) {
8
+ const d = typeof date === "string" ? new Date(date) : date;
9
+ return d.toLocaleDateString("en-US", {
10
+ month: "short",
11
+ day: "numeric",
12
+ year: "numeric",
13
+ });
14
+ }
15
+ /**
16
+ * Format status with emoji
17
+ */
18
+ export function formatStatus(status) {
19
+ const statusEmojis = {
20
+ // Content statuses
21
+ draft: "📝",
22
+ generating: "🔄",
23
+ review: "👀",
24
+ published: "✅",
25
+ archived: "📦",
26
+ // Brief statuses
27
+ pending: "⏳",
28
+ research: "🔍",
29
+ ready: "✅",
30
+ completed: "✅",
31
+ // Job statuses
32
+ running: "🔄",
33
+ failed: "❌",
34
+ success: "✅",
35
+ // Audit statuses
36
+ crawling: "🕷️",
37
+ analyzing: "🔍",
38
+ };
39
+ const emoji = statusEmojis[status.toLowerCase()] || "•";
40
+ return `${emoji} ${status}`;
41
+ }
42
+ /**
43
+ * Format a list of items as a markdown table
44
+ */
45
+ export function formatTable(headers, rows, options) {
46
+ if (rows.length === 0) {
47
+ return "_No items found._";
48
+ }
49
+ const align = options?.align || headers.map(() => "left");
50
+ // Build header row
51
+ let table = "| " + headers.join(" | ") + " |\n";
52
+ // Build separator row with alignment
53
+ table +=
54
+ "| " +
55
+ align
56
+ .map((a) => {
57
+ switch (a) {
58
+ case "center":
59
+ return ":---:";
60
+ case "right":
61
+ return "---:";
62
+ default:
63
+ return "---";
64
+ }
65
+ })
66
+ .join(" | ") +
67
+ " |\n";
68
+ // Build data rows
69
+ for (const row of rows) {
70
+ table += "| " + row.join(" | ") + " |\n";
71
+ }
72
+ return table;
73
+ }
74
+ /**
75
+ * Format pagination info
76
+ */
77
+ export function formatPagination(pagination) {
78
+ const start = (pagination.page - 1) * pagination.page_size + 1;
79
+ const end = Math.min(pagination.page * pagination.page_size, pagination.total);
80
+ let text = `\n**Showing ${start}-${end} of ${pagination.total} items**`;
81
+ if (pagination.has_more) {
82
+ text += ` · \`has_more: true\`\n`;
83
+ text += `\nUse \`page: ${pagination.page + 1}\` to see more.`;
84
+ }
85
+ return text;
86
+ }
87
+ /**
88
+ * Format a success response
89
+ */
90
+ export function formatSuccess(title, content) {
91
+ return `## ${title}\n\n${content}`;
92
+ }
93
+ /**
94
+ * Format an error response
95
+ */
96
+ export function formatError(error) {
97
+ const message = typeof error === "string" ? error : error.message;
98
+ return `**Error:** ${message}`;
99
+ }
100
+ /**
101
+ * Format a key-value list
102
+ */
103
+ export function formatKeyValue(items) {
104
+ const lines = [];
105
+ for (const [key, value] of Object.entries(items)) {
106
+ if (value !== undefined && value !== null) {
107
+ const displayKey = key
108
+ .replace(/_/g, " ")
109
+ .replace(/\b\w/g, (c) => c.toUpperCase());
110
+ lines.push(`- **${displayKey}:** ${value}`);
111
+ }
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+ /**
116
+ * Format word count
117
+ */
118
+ export function formatWordCount(count) {
119
+ if (count === null || count === undefined) {
120
+ return "-";
121
+ }
122
+ if (count >= 1000) {
123
+ return `${(count / 1000).toFixed(1)}k`;
124
+ }
125
+ return count.toString();
126
+ }
127
+ /**
128
+ * Truncate text with ellipsis
129
+ */
130
+ export function truncate(text, maxLength) {
131
+ if (text.length <= maxLength) {
132
+ return text;
133
+ }
134
+ return text.slice(0, maxLength - 3) + "...";
135
+ }
136
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Frase MCP Server
4
+ *
5
+ * A Model Context Protocol (MCP) server that allows customers to interact with
6
+ * their Frase account via AI tools like Claude Desktop.
7
+ *
8
+ * Features:
9
+ * - API key authentication
10
+ * - All v1 API functionality as MCP tools
11
+ * - Markdown-formatted responses for easy reading
12
+ * - Local caching for improved performance
13
+ * - Auto-retry with exponential backoff
14
+ *
15
+ * Usage:
16
+ * Set FRASE_API_KEY environment variable before connecting
17
+ *
18
+ * Claude Desktop config example:
19
+ * {
20
+ * "mcpServers": {
21
+ * "frase": {
22
+ * "command": "npx",
23
+ * "args": ["-y", "@frase/mcp-server"],
24
+ * "env": {
25
+ * "FRASE_API_KEY": "your-api-key-here"
26
+ * }
27
+ * }
28
+ * }
29
+ * }
30
+ */
31
+ export {};
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG"}
package/dist/index.js ADDED
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Frase MCP Server
4
+ *
5
+ * A Model Context Protocol (MCP) server that allows customers to interact with
6
+ * their Frase account via AI tools like Claude Desktop.
7
+ *
8
+ * Features:
9
+ * - API key authentication
10
+ * - All v1 API functionality as MCP tools
11
+ * - Markdown-formatted responses for easy reading
12
+ * - Local caching for improved performance
13
+ * - Auto-retry with exponential backoff
14
+ *
15
+ * Usage:
16
+ * Set FRASE_API_KEY environment variable before connecting
17
+ *
18
+ * Claude Desktop config example:
19
+ * {
20
+ * "mcpServers": {
21
+ * "frase": {
22
+ * "command": "npx",
23
+ * "args": ["-y", "@frase/mcp-server"],
24
+ * "env": {
25
+ * "FRASE_API_KEY": "your-api-key-here"
26
+ * }
27
+ * }
28
+ * }
29
+ * }
30
+ */
31
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
32
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
34
+ import { config as loadEnv } from "dotenv";
35
+ // Load environment variables
36
+ loadEnv();
37
+ import { loadConfig, debugLog } from "./config.js";
38
+ import { FraseApiClient } from "./api-client.js";
39
+ import { ALL_TOOLS, TOOL_EXECUTORS, getToolByName } from "./tools/index.js";
40
+ import { ALL_RESOURCE_TEMPLATES, listAllResources, readResource, getRootResources, } from "./resources/index.js";
41
+ import { ALL_PROMPTS, getPromptByName, getPromptMessages, } from "./prompts/index.js";
42
+ // Server metadata
43
+ const SERVER_NAME = "frase-mcp-server";
44
+ const SERVER_VERSION = "0.1.0";
45
+ /**
46
+ * Main MCP server class
47
+ */
48
+ class FraseMcpServer {
49
+ server;
50
+ config = null;
51
+ client = null;
52
+ isInitialized = false;
53
+ constructor() {
54
+ this.server = new Server({
55
+ name: SERVER_NAME,
56
+ version: SERVER_VERSION,
57
+ }, {
58
+ capabilities: {
59
+ tools: {},
60
+ resources: {},
61
+ prompts: {},
62
+ },
63
+ });
64
+ this.setupHandlers();
65
+ this.setupErrorHandling();
66
+ }
67
+ /**
68
+ * Initialize the server (load config, create API client)
69
+ */
70
+ async initialize() {
71
+ if (this.isInitialized) {
72
+ return;
73
+ }
74
+ try {
75
+ this.config = loadConfig();
76
+ this.client = new FraseApiClient(this.config);
77
+ this.isInitialized = true;
78
+ debugLog(this.config, "Server initialized");
79
+ debugLog(this.config, `API URL: ${this.config.apiUrl}`);
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : "Unknown error";
83
+ throw new McpError(ErrorCode.InvalidRequest, message);
84
+ }
85
+ }
86
+ /**
87
+ * Get tool context for execution
88
+ */
89
+ getContext() {
90
+ if (!this.config || !this.client) {
91
+ throw new McpError(ErrorCode.InvalidRequest, "Server not initialized");
92
+ }
93
+ return {
94
+ client: this.client,
95
+ config: this.config,
96
+ };
97
+ }
98
+ /**
99
+ * Set up MCP request handlers
100
+ */
101
+ setupHandlers() {
102
+ // Handle list_tools request
103
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
104
+ await this.initialize();
105
+ return {
106
+ tools: ALL_TOOLS.map((tool) => ({
107
+ name: tool.name,
108
+ description: tool.description,
109
+ inputSchema: tool.inputSchema,
110
+ })),
111
+ };
112
+ });
113
+ // Handle list_resources request
114
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
115
+ await this.initialize();
116
+ try {
117
+ const context = this.getContext();
118
+ const resources = await listAllResources(context);
119
+ return {
120
+ resources: resources.map((r) => ({
121
+ uri: r.uri,
122
+ name: r.name,
123
+ description: r.description,
124
+ mimeType: r.mimeType,
125
+ })),
126
+ };
127
+ }
128
+ catch (error) {
129
+ console.error("[Frase MCP] Error listing resources:", error);
130
+ // Return root resources as fallback
131
+ return {
132
+ resources: getRootResources().map((r) => ({
133
+ uri: r.uri,
134
+ name: r.name,
135
+ description: r.description,
136
+ mimeType: r.mimeType,
137
+ })),
138
+ };
139
+ }
140
+ });
141
+ // Handle list_resource_templates request
142
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
143
+ return {
144
+ resourceTemplates: ALL_RESOURCE_TEMPLATES.map((t) => ({
145
+ uriTemplate: t.uriTemplate,
146
+ name: t.name,
147
+ description: t.description,
148
+ mimeType: t.mimeType,
149
+ })),
150
+ };
151
+ });
152
+ // Handle read_resource request
153
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
154
+ await this.initialize();
155
+ const { uri } = request.params;
156
+ try {
157
+ debugLog(this.config, `Reading resource: ${uri}`);
158
+ const context = this.getContext();
159
+ const result = await readResource(uri, context);
160
+ return result;
161
+ }
162
+ catch (error) {
163
+ console.error(`[Frase MCP] Error reading resource:`, error);
164
+ const message = error instanceof Error ? error.message : "Unknown error";
165
+ throw new McpError(ErrorCode.InvalidRequest, message);
166
+ }
167
+ });
168
+ // Handle list_prompts request
169
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
170
+ return {
171
+ prompts: ALL_PROMPTS.map((p) => ({
172
+ name: p.name,
173
+ description: p.description,
174
+ arguments: p.arguments,
175
+ })),
176
+ };
177
+ });
178
+ // Handle get_prompt request
179
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
180
+ const { name, arguments: promptArgs } = request.params;
181
+ const prompt = getPromptByName(name);
182
+ if (!prompt) {
183
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown prompt: ${name}`);
184
+ }
185
+ try {
186
+ const messages = getPromptMessages(name, promptArgs || {});
187
+ return {
188
+ description: prompt.description,
189
+ messages,
190
+ };
191
+ }
192
+ catch (error) {
193
+ console.error(`[Frase MCP] Error getting prompt:`, error);
194
+ const message = error instanceof Error ? error.message : "Unknown error";
195
+ throw new McpError(ErrorCode.InvalidRequest, message);
196
+ }
197
+ });
198
+ // Handle call_tool request
199
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
200
+ await this.initialize();
201
+ const { name, arguments: args } = request.params;
202
+ // Find the tool
203
+ const tool = getToolByName(name);
204
+ if (!tool) {
205
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
206
+ }
207
+ // Get the executor
208
+ const executor = TOOL_EXECUTORS[name];
209
+ if (!executor) {
210
+ throw new McpError(ErrorCode.InternalError, `No executor for tool: ${name}`);
211
+ }
212
+ // Execute the tool
213
+ try {
214
+ debugLog(this.config, `Executing tool: ${name}`);
215
+ const startTime = Date.now();
216
+ const context = this.getContext();
217
+ const result = await executor(args, context);
218
+ const duration = Date.now() - startTime;
219
+ debugLog(this.config, `Tool ${name} completed in ${duration}ms`);
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: result.markdown,
225
+ },
226
+ ],
227
+ isError: !result.success,
228
+ };
229
+ }
230
+ catch (error) {
231
+ console.error(`[Frase MCP] Tool execution error:`, error);
232
+ const message = error instanceof Error ? error.message : "Unknown error";
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: `**Error:** ${message}`,
238
+ },
239
+ ],
240
+ isError: true,
241
+ };
242
+ }
243
+ });
244
+ }
245
+ /**
246
+ * Set up error handling
247
+ */
248
+ setupErrorHandling() {
249
+ this.server.onerror = (error) => {
250
+ console.error("[Frase MCP] Server error:", error);
251
+ };
252
+ process.on("SIGINT", async () => {
253
+ console.error("[Frase MCP] Shutting down...");
254
+ this.cleanup();
255
+ await this.server.close();
256
+ process.exit(0);
257
+ });
258
+ process.on("SIGTERM", async () => {
259
+ console.error("[Frase MCP] Shutting down...");
260
+ this.cleanup();
261
+ await this.server.close();
262
+ process.exit(0);
263
+ });
264
+ }
265
+ /**
266
+ * Clean up resources
267
+ */
268
+ cleanup() {
269
+ if (this.client) {
270
+ this.client.destroy();
271
+ }
272
+ }
273
+ /**
274
+ * Start the MCP server
275
+ */
276
+ async run() {
277
+ const transport = new StdioServerTransport();
278
+ await this.server.connect(transport);
279
+ console.error(`[Frase MCP] Server started (v${SERVER_VERSION})`);
280
+ console.error(`[Frase MCP] Tools available: ${ALL_TOOLS.length}`);
281
+ console.error(`[Frase MCP] Resource templates: ${ALL_RESOURCE_TEMPLATES.length}`);
282
+ console.error(`[Frase MCP] Prompts available: ${ALL_PROMPTS.length}`);
283
+ console.error("[Frase MCP] Waiting for connection...");
284
+ }
285
+ }
286
+ // Entry point
287
+ const server = new FraseMcpServer();
288
+ server.run().catch((error) => {
289
+ console.error("[Frase MCP] Fatal error:", error);
290
+ process.exit(1);
291
+ });
292
+ //# sourceMappingURL=index.js.map