@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/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-02-01
9
+
10
+ ### Added
11
+
12
+ #### Tools (38 total)
13
+ - **Sites**: `list_sites`
14
+ - **Briefs**: `list_briefs`, `create_brief`, `get_brief`
15
+ - **Content**: `list_content`, `get_content`
16
+ - **Research**: `list_research`, `start_research`
17
+ - **Jobs**: `get_job_status`
18
+ - **Audits**: `list_audits`, `start_audit`, `get_audit`, `export_audit`
19
+ - **SERP**: `analyze_serp`, `analyze_competitors`, `get_search_intent`
20
+ - **AI Visibility**: `get_ai_visibility`, `list_prompts`, `create_prompt`, `get_prompt`, `update_prompt`, `get_competitors`, `get_alerts`, `get_insights`
21
+ - **Analytics**: `get_gsc_overview`, `get_gsc_queries`, `get_gsc_pages`, `get_content_gaps`
22
+ - **Optimizations**: `list_optimizations`, `start_optimization`, `get_optimization`, `apply_optimization`, `get_optimization_insights`
23
+ - **Webhooks**: `list_webhooks`, `create_webhook`, `get_webhook`, `update_webhook`, `delete_webhook`
24
+
25
+ #### Resources
26
+ - `frase://sites` - List all sites
27
+ - `frase://sites/{id}` - Individual site details
28
+ - `frase://content` - List all content
29
+ - `frase://content/{id}` - Individual content with body
30
+ - `frase://briefs` - List all briefs
31
+ - `frase://briefs/{id}` - Individual brief with outline
32
+
33
+ #### Prompts
34
+ - `create_seo_article` - Full SEO article creation workflow
35
+ - `optimize_content` - Content optimization workflow
36
+ - `keyword_research` - Keyword research workflow
37
+ - `competitor_analysis` - Competitor analysis workflow
38
+ - `content_audit` - Site content audit workflow
39
+
40
+ #### Features
41
+ - API client with retry logic and exponential backoff
42
+ - Response caching (60s for lists, 5min for resources)
43
+ - Markdown-formatted responses for better readability
44
+ - Debug mode via `FRASE_MCP_DEBUG` environment variable
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # @frase/mcp-server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@frase/mcp-server.svg)](https://www.npmjs.com/package/@frase/mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Use Claude Desktop to interact with your Frase account. This MCP (Model Context Protocol) server exposes Frase's API as tools that Claude can use to help you with SEO content creation, research, and optimization.
7
+
8
+ ## What is MCP?
9
+
10
+ [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that allows AI assistants like Claude to securely connect to external tools and data sources. This server implements MCP to give Claude access to your Frase account.
11
+
12
+ ## Quick Start
13
+
14
+ ### 1. Get your API key
15
+
16
+ Get your API key from [Frase Settings](https://app.frase.io/settings/api).
17
+
18
+ ### 2. Configure Claude Desktop
19
+
20
+ Add this to your Claude Desktop config file:
21
+
22
+ **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
23
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "frase": {
29
+ "command": "npx",
30
+ "args": ["-y", "@frase/mcp-server"],
31
+ "env": {
32
+ "FRASE_API_KEY": "sk_live_your_api_key_here"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### 3. Restart Claude Desktop
40
+
41
+ After saving the config, restart Claude Desktop. You should see "frase" in the MCP servers list.
42
+
43
+ ## Available Tools
44
+
45
+ ### Sites
46
+ - `list_sites` - List all sites in your account
47
+
48
+ ### Briefs
49
+ - `list_briefs` - List content briefs with optional status filter
50
+ - `create_brief` - Create a new brief (with optional auto-outline generation)
51
+ - `get_brief` - Get brief details including outline and SERP data
52
+
53
+ ### Content
54
+ - `list_content` - List content items with optional status filter
55
+ - `get_content` - Get content details including full body text
56
+
57
+ ### Research
58
+ - `list_research` - List research sessions
59
+ - `start_research` - Start new AI-powered research on a topic
60
+
61
+ ### Jobs
62
+ - `get_job_status` - Check status of async jobs (briefs, research, audits, etc.)
63
+
64
+ ### Audits
65
+ - `list_audits` - List site audits
66
+ - `start_audit` - Start a new site audit
67
+ - `get_audit` - Get audit details with pages and issues
68
+ - `export_audit` - Export audit data as CSV
69
+
70
+ ### SERP Analysis
71
+ - `analyze_serp` - Analyze search engine results for a query
72
+ - `analyze_competitors` - Analyze competitor content for a query
73
+ - `get_search_intent` - Get search intent classification for a query
74
+
75
+ ### AI Visibility
76
+ - `get_ai_visibility` - Get AI visibility metrics for a site
77
+ - `list_prompts` - List monitored AI prompts
78
+ - `create_prompt` - Create a new monitored prompt
79
+ - `get_prompt` - Get prompt details with citations
80
+ - `update_prompt` - Update a monitored prompt
81
+ - `get_competitors` - Get AI visibility competitors
82
+ - `get_alerts` - Get AI visibility alerts
83
+ - `get_insights` - Get AI visibility insights
84
+
85
+ ### Analytics
86
+ - `get_gsc_overview` - Get Google Search Console overview metrics
87
+ - `get_gsc_queries` - Get top search queries from GSC
88
+ - `get_gsc_pages` - Get top performing pages from GSC
89
+ - `get_content_gaps` - Get content gap opportunities
90
+
91
+ ### Optimizations
92
+ - `list_optimizations` - List optimization sessions
93
+ - `start_optimization` - Start optimization analysis for content
94
+ - `get_optimization` - Get optimization details with suggestions
95
+ - `apply_optimization` - Apply optimization suggestions
96
+ - `get_optimization_insights` - Get optimization insights
97
+
98
+ ### Webhooks
99
+ - `list_webhooks` - List configured webhooks
100
+ - `create_webhook` - Create a new webhook
101
+ - `get_webhook` - Get webhook details
102
+ - `update_webhook` - Update a webhook
103
+ - `delete_webhook` - Delete a webhook
104
+
105
+ ## Example Conversations
106
+
107
+ ### List your sites
108
+ ```
109
+ You: What sites do I have connected?
110
+ Claude: [Uses list_sites tool]
111
+
112
+ Here are your connected sites:
113
+
114
+ | Name | Domain | GSC | Created |
115
+ |------|--------|-----|---------|
116
+ | Main Blog | example.com | ✅ | Jan 15, 2024 |
117
+ | Product Site | product.example.com | ❌ | Feb 20, 2024 |
118
+ ```
119
+
120
+ ### Create a brief with auto-outline
121
+ ```
122
+ You: Create a brief about React performance optimization
123
+ Claude: [Uses create_brief with generate_outline=true]
124
+
125
+ ## Brief Created
126
+
127
+ - **ID:** abc123
128
+ - **Topic:** React performance optimization
129
+ - **Status:** pending
130
+
131
+ **Outline generation started.** Let me check the status...
132
+
133
+ [Uses get_job_status]
134
+
135
+ The outline is ready! Let me get the details...
136
+
137
+ [Uses get_brief]
138
+
139
+ ## Brief: React performance optimization
140
+
141
+ ### Outline
142
+ - Introduction to React Performance
143
+ - Common Performance Bottlenecks
144
+ - Optimization Techniques
145
+ - Measuring Performance
146
+ - ...
147
+ ```
148
+
149
+ ### Research a topic
150
+ ```
151
+ You: Research best practices for Next.js SEO
152
+ Claude: [Uses start_research]
153
+
154
+ ## Research Started
155
+
156
+ - **ID:** xyz789
157
+ - **Query:** best practices for Next.js SEO
158
+ - **Status:** pending
159
+
160
+ I'll check on the progress...
161
+ ```
162
+
163
+ ## MCP Resources
164
+
165
+ Browse your Frase data directly in Claude Desktop:
166
+
167
+ - `frase://sites` - List all your sites
168
+ - `frase://sites/{id}` - View individual site details
169
+ - `frase://content` - List all content items
170
+ - `frase://content/{id}` - View content with full body
171
+ - `frase://briefs` - List all briefs
172
+ - `frase://briefs/{id}` - View brief with outline
173
+
174
+ ## MCP Prompts
175
+
176
+ Pre-built workflows for common SEO tasks:
177
+
178
+ | Prompt | Description |
179
+ |--------|-------------|
180
+ | `create_seo_article` | Full workflow: research, brief, outline, content generation |
181
+ | `optimize_content` | Analyze content and apply optimization suggestions |
182
+ | `keyword_research` | Research keywords, SERP analysis, and content opportunities |
183
+ | `competitor_analysis` | Analyze competitor content and find gaps |
184
+ | `content_audit` | Run site audit and prioritize improvements |
185
+
186
+ ## Configuration
187
+
188
+ | Variable | Required | Description |
189
+ |----------|----------|-------------|
190
+ | `FRASE_API_KEY` | Yes | Your Frase API key |
191
+ | `FRASE_API_URL` | No | Override API URL (default: `https://next.frase.io/api/v1`) |
192
+ | `FRASE_MCP_DEBUG` | No | Enable debug logging (`true`/`false`) |
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ # Install dependencies
198
+ npm install
199
+
200
+ # Run in development mode
201
+ npm run dev
202
+
203
+ # Build
204
+ npm run build
205
+
206
+ # Run tests
207
+ npm test
208
+
209
+ # Run integration tests (requires FRASE_TEST_API_KEY)
210
+ npm run test:integration
211
+ ```
212
+
213
+ ## Troubleshooting
214
+
215
+ ### Server not appearing in Claude Desktop
216
+
217
+ 1. Verify your config file location:
218
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
219
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
220
+
221
+ 2. Check JSON syntax - use a validator if needed
222
+
223
+ 3. Restart Claude Desktop completely (quit and reopen)
224
+
225
+ ### Authentication errors
226
+
227
+ - Verify your API key is correct
228
+ - Ensure the key has not expired
229
+ - Check that the key has the required permissions
230
+
231
+ ### Debug mode
232
+
233
+ Enable debug logging to see detailed information:
234
+
235
+ ```json
236
+ {
237
+ "mcpServers": {
238
+ "frase": {
239
+ "command": "npx",
240
+ "args": ["-y", "@frase/mcp-server"],
241
+ "env": {
242
+ "FRASE_API_KEY": "your_key",
243
+ "FRASE_MCP_DEBUG": "true"
244
+ }
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ ### Rate limiting
251
+
252
+ The server automatically retries on rate limits with exponential backoff. If you're hitting limits frequently, consider spacing out your requests.
253
+
254
+ ## Support
255
+
256
+ - [Frase Documentation](https://docs.frase.io)
257
+ - [API Documentation](https://docs.frase.io/api)
258
+ - [GitHub Issues](https://github.com/frase-io/frase/issues)
259
+
260
+ ## License
261
+
262
+ MIT
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Frase API Client with retry logic and caching
3
+ */
4
+ import type { Config } from "./config.js";
5
+ /**
6
+ * API Error class
7
+ */
8
+ export declare class FraseApiError extends Error {
9
+ readonly status: number;
10
+ readonly code: string;
11
+ readonly details?: unknown | undefined;
12
+ constructor(status: number, code: string, message: string, details?: unknown | undefined);
13
+ /**
14
+ * Check if this error is retryable
15
+ */
16
+ get isRetryable(): boolean;
17
+ /**
18
+ * Format error for display
19
+ */
20
+ toMarkdown(): string;
21
+ }
22
+ /**
23
+ * Paginated response structure
24
+ */
25
+ export interface PaginatedResponse<T> {
26
+ data: T[];
27
+ pagination: {
28
+ page: number;
29
+ page_size: number;
30
+ total: number;
31
+ has_more: boolean;
32
+ };
33
+ }
34
+ /**
35
+ * API response structure
36
+ */
37
+ export interface ApiResponse<T> {
38
+ data: T;
39
+ request_id: string;
40
+ }
41
+ /**
42
+ * Frase API Client
43
+ */
44
+ export declare class FraseApiClient {
45
+ private config;
46
+ private cache;
47
+ constructor(config: Config);
48
+ /**
49
+ * Sleep for a specified duration
50
+ */
51
+ private sleep;
52
+ /**
53
+ * Calculate retry delay with exponential backoff
54
+ */
55
+ private getRetryDelay;
56
+ /**
57
+ * Make an HTTP request with retry logic
58
+ */
59
+ request<T>(method: string, path: string, options?: {
60
+ body?: unknown;
61
+ params?: Record<string, string | number | boolean | undefined>;
62
+ cacheTtl?: number;
63
+ }): Promise<T>;
64
+ /**
65
+ * GET request
66
+ */
67
+ get<T>(path: string, params?: Record<string, string | number | boolean | undefined>, cacheTtl?: number): Promise<T>;
68
+ /**
69
+ * POST request
70
+ */
71
+ post<T>(path: string, body: unknown): Promise<T>;
72
+ /**
73
+ * PUT request
74
+ */
75
+ put<T>(path: string, body: unknown): Promise<T>;
76
+ /**
77
+ * PATCH request
78
+ */
79
+ patch<T>(path: string, body: unknown): Promise<T>;
80
+ /**
81
+ * DELETE request
82
+ */
83
+ delete<T>(path: string): Promise<T>;
84
+ /**
85
+ * Clear all cached data
86
+ */
87
+ clearCache(): void;
88
+ /**
89
+ * Destroy the client (for graceful shutdown)
90
+ */
91
+ destroy(): void;
92
+ }
93
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAa1C;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;aAEpB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;aAEZ,OAAO,CAAC,EAAE,OAAO;gBAHjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,OAAO,YAAA;IAMnC;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACH,UAAU,IAAI,MAAM;CAWrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC;IACR,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,cAAc;IAGb,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,KAAK,CAAgB;gBAET,MAAM,EAAE,MAAM;IAIlC;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;OAEG;IACG,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;QAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC,CAAC,CAAC;IAiHb;;OAEG;IACH,GAAG,CAAC,CAAC,EACH,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,EAC9D,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,CAAC;IAIb;;OAEG;IACH,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhD;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/C;;OAEG;IACH,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIjD;;OAEG;IACH,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAInC;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Frase API Client with retry logic and caching
3
+ */
4
+ import { ResponseCache } from "./cache.js";
5
+ import { debugLog } from "./config.js";
6
+ /**
7
+ * Retry configuration
8
+ */
9
+ const RETRY_CONFIG = {
10
+ maxRetries: 2,
11
+ baseDelayMs: 1000,
12
+ maxDelayMs: 10000,
13
+ retryableStatuses: [429, 500, 502, 503, 504],
14
+ };
15
+ /**
16
+ * API Error class
17
+ */
18
+ export class FraseApiError extends Error {
19
+ status;
20
+ code;
21
+ details;
22
+ constructor(status, code, message, details) {
23
+ super(message);
24
+ this.status = status;
25
+ this.code = code;
26
+ this.details = details;
27
+ this.name = "FraseApiError";
28
+ }
29
+ /**
30
+ * Check if this error is retryable
31
+ */
32
+ get isRetryable() {
33
+ return RETRY_CONFIG.retryableStatuses.includes(this.status);
34
+ }
35
+ /**
36
+ * Format error for display
37
+ */
38
+ toMarkdown() {
39
+ let md = `**Error:** ${this.message}\n`;
40
+ md += `- Code: \`${this.code}\`\n`;
41
+ md += `- Status: ${this.status}\n`;
42
+ if (this.details) {
43
+ md += `- Details: ${JSON.stringify(this.details)}\n`;
44
+ }
45
+ return md;
46
+ }
47
+ }
48
+ /**
49
+ * Frase API Client
50
+ */
51
+ export class FraseApiClient {
52
+ config;
53
+ cache;
54
+ constructor(config) {
55
+ this.config = config;
56
+ this.cache = new ResponseCache();
57
+ }
58
+ /**
59
+ * Sleep for a specified duration
60
+ */
61
+ sleep(ms) {
62
+ return new Promise((resolve) => setTimeout(resolve, ms));
63
+ }
64
+ /**
65
+ * Calculate retry delay with exponential backoff
66
+ */
67
+ getRetryDelay(attempt, retryAfter) {
68
+ if (retryAfter) {
69
+ const seconds = parseInt(retryAfter, 10);
70
+ if (!isNaN(seconds)) {
71
+ return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs);
72
+ }
73
+ }
74
+ // Exponential backoff: 1s, 2s, 4s... capped at maxDelayMs
75
+ const delay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
76
+ return Math.min(delay, RETRY_CONFIG.maxDelayMs);
77
+ }
78
+ /**
79
+ * Make an HTTP request with retry logic
80
+ */
81
+ async request(method, path, options) {
82
+ const { body, params, cacheTtl } = options || {};
83
+ // Build URL with query params
84
+ let url = `${this.config.apiUrl}${path}`;
85
+ if (params) {
86
+ const searchParams = new URLSearchParams();
87
+ for (const [key, value] of Object.entries(params)) {
88
+ if (value !== undefined) {
89
+ searchParams.set(key, String(value));
90
+ }
91
+ }
92
+ const queryString = searchParams.toString();
93
+ if (queryString) {
94
+ url += `?${queryString}`;
95
+ }
96
+ }
97
+ // Check cache for GET requests
98
+ if (method === "GET" && cacheTtl) {
99
+ const cacheKey = ResponseCache.key(method, path, params);
100
+ const cached = this.cache.get(cacheKey);
101
+ if (cached) {
102
+ debugLog(this.config, `Cache hit: ${path}`);
103
+ return cached;
104
+ }
105
+ }
106
+ let lastError = null;
107
+ for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
108
+ try {
109
+ debugLog(this.config, `Request: ${method} ${path} (attempt ${attempt + 1})`);
110
+ const response = await fetch(url, {
111
+ method,
112
+ headers: {
113
+ "Content-Type": "application/json",
114
+ "X-API-KEY": this.config.apiKey,
115
+ "User-Agent": "frase-mcp-server/0.1.0",
116
+ },
117
+ body: body ? JSON.stringify(body) : undefined,
118
+ });
119
+ if (!response.ok) {
120
+ let errorData = {};
121
+ try {
122
+ errorData = await response.json();
123
+ }
124
+ catch {
125
+ // Ignore JSON parse errors
126
+ }
127
+ const error = new FraseApiError(response.status, errorData.error?.code || `http_${response.status}`, errorData.error?.message || `HTTP ${response.status}`, errorData);
128
+ // Check if we should retry
129
+ if (attempt < RETRY_CONFIG.maxRetries && error.isRetryable) {
130
+ const delay = this.getRetryDelay(attempt, response.headers.get("Retry-After") || undefined);
131
+ debugLog(this.config, `Retrying in ${delay}ms...`);
132
+ await this.sleep(delay);
133
+ continue;
134
+ }
135
+ throw error;
136
+ }
137
+ const data = (await response.json());
138
+ // Cache successful GET responses
139
+ if (method === "GET" && cacheTtl) {
140
+ const cacheKey = ResponseCache.key(method, path, params);
141
+ this.cache.set(cacheKey, data, cacheTtl);
142
+ }
143
+ // Invalidate cache on write operations
144
+ if (method !== "GET") {
145
+ const resourceType = path.split("/")[1]; // e.g., /sites -> sites
146
+ if (resourceType) {
147
+ this.cache.invalidateResourceType(resourceType);
148
+ }
149
+ }
150
+ return data;
151
+ }
152
+ catch (error) {
153
+ lastError = error;
154
+ // Don't retry FraseApiError unless it's retryable
155
+ if (error instanceof FraseApiError && !error.isRetryable) {
156
+ throw error;
157
+ }
158
+ // Network errors - retry if we have attempts left
159
+ if (attempt < RETRY_CONFIG.maxRetries && !(error instanceof FraseApiError)) {
160
+ const delay = this.getRetryDelay(attempt);
161
+ debugLog(this.config, `Network error, retrying in ${delay}ms...`);
162
+ await this.sleep(delay);
163
+ continue;
164
+ }
165
+ throw error;
166
+ }
167
+ }
168
+ throw lastError || new Error("Request failed after retries");
169
+ }
170
+ /**
171
+ * GET request
172
+ */
173
+ get(path, params, cacheTtl) {
174
+ return this.request("GET", path, { params, cacheTtl });
175
+ }
176
+ /**
177
+ * POST request
178
+ */
179
+ post(path, body) {
180
+ return this.request("POST", path, { body });
181
+ }
182
+ /**
183
+ * PUT request
184
+ */
185
+ put(path, body) {
186
+ return this.request("PUT", path, { body });
187
+ }
188
+ /**
189
+ * PATCH request
190
+ */
191
+ patch(path, body) {
192
+ return this.request("PATCH", path, { body });
193
+ }
194
+ /**
195
+ * DELETE request
196
+ */
197
+ delete(path) {
198
+ return this.request("DELETE", path);
199
+ }
200
+ /**
201
+ * Clear all cached data
202
+ */
203
+ clearCache() {
204
+ this.cache.clear();
205
+ }
206
+ /**
207
+ * Destroy the client (for graceful shutdown)
208
+ */
209
+ destroy() {
210
+ this.cache.destroy();
211
+ }
212
+ }
213
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Simple in-memory cache for GET responses
3
+ */
4
+ /**
5
+ * Cache TTL configuration (in milliseconds)
6
+ */
7
+ export declare const CACHE_TTL: {
8
+ readonly lists: 60000;
9
+ readonly resources: 300000;
10
+ };
11
+ /**
12
+ * Simple cache implementation
13
+ */
14
+ export declare class ResponseCache {
15
+ private cache;
16
+ private cleanupInterval;
17
+ constructor();
18
+ /**
19
+ * Get a cached value
20
+ */
21
+ get<T>(key: string): T | undefined;
22
+ /**
23
+ * Set a cached value
24
+ */
25
+ set<T>(key: string, data: T, ttl: number): void;
26
+ /**
27
+ * Generate cache key for a request
28
+ */
29
+ static key(method: string, path: string, params?: Record<string, unknown>): string;
30
+ /**
31
+ * Invalidate cache entries matching a pattern
32
+ */
33
+ invalidate(pattern: string | RegExp): void;
34
+ /**
35
+ * Invalidate all cache entries for a resource type
36
+ * Called after write operations
37
+ */
38
+ invalidateResourceType(resourceType: string): void;
39
+ /**
40
+ * Clear all cached entries
41
+ */
42
+ clear(): void;
43
+ /**
44
+ * Clean up expired entries
45
+ */
46
+ private cleanup;
47
+ /**
48
+ * Stop the cleanup interval (for graceful shutdown)
49
+ */
50
+ destroy(): void;
51
+ }
52
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,eAAO,MAAM,SAAS;;;CAGZ,CAAC;AAEX;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,eAAe,CAA+B;;IAOtD;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAelC;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAO/C;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAKlF;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAU1C;;;OAGG;IACH,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAIlD;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,CAAC,OAAO;IAUf;;OAEG;IACH,OAAO,IAAI,IAAI;CAOhB"}