latitude-mcp-server 3.1.0 → 3.2.1

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.
@@ -0,0 +1,163 @@
1
+ ---
2
+ description: Complete guide for LLMs to autonomously manage, test, and optimize PromptL prompts via MCP
3
+ auto_execution_mode: 3
4
+ ---
5
+
6
+ # LATITUDE MCP SERVER - LLM GUIDE
7
+
8
+ > "Autonomous prompt engineering: Create → Validate → Test → Iterate → Optimize"
9
+
10
+ ## CRITICAL WORKFLOW
11
+
12
+ ```
13
+ 1. pull_prompts → Download all prompts to ./prompts/*.promptl
14
+ 2. Edit locally → Your IDE, full context
15
+ 3. add_prompt → Push with validation (overwrites if exists)
16
+ 4. run_prompt → Test with parameters
17
+ 5. Iterate → Analyze output → improve → re-push → re-test
18
+ ```
19
+
20
+ **7 MCP tools. Client-side validation. Dynamic descriptions. Git-style versioning.**
21
+
22
+ ---
23
+
24
+ ## THE 7 TOOLS
25
+
26
+ | Tool | Type | Purpose | Dynamic Feature |
27
+ |------|------|---------|-----------------|
28
+ | `list_prompts` | Read | List all prompt names in LIVE | — |
29
+ | `get_prompt` | Read | Get full prompt content by name | — |
30
+ | `run_prompt` | Execute | Execute prompt with parameters | 🎯 Shows all prompts with their params |
31
+ | `pull_prompts` | Sync | Download LIVE → `./prompts/*.promptl` (FULL SYNC) | — |
32
+ | `add_prompt` | Write | Add/update prompts (overwrites if exists, never deletes others) | 🎯 Shows available prompts |
33
+ | `push_prompts` | Sync | Replace ALL prompts (FULL SYNC, deletes extras) | — |
34
+ | `docs` | Read | Documentation (52 topics, semantic search) | — |
35
+
36
+ ### 🎯 Dynamic Descriptions
37
+
38
+ **run_prompt** shows you what parameters each prompt needs:
39
+ ```
40
+ Available prompts (10):
41
+ - email-writer (params: recipient, topic, tone)
42
+ - sentiment-analyzer (no params)
43
+ ```
44
+
45
+ **add_prompt** shows you what prompts already exist:
46
+ ```
47
+ Available prompts (10): email-writer, sentiment-analyzer, ...
48
+ ```
49
+
50
+ ---
51
+
52
+ ## AUTONOMOUS WORKFLOWS
53
+
54
+ ### Create → Test → Iterate
55
+
56
+ ```javascript
57
+ // 1. Create prompt
58
+ add_prompt({
59
+ prompts: [{
60
+ name: "email-extractor",
61
+ content: `---
62
+ provider: openai
63
+ model: gpt-4o
64
+ temperature: 0.2
65
+ schema:
66
+ type: object
67
+ properties:
68
+ email: { type: string }
69
+ required: [email]
70
+ ---
71
+ <user>Extract from: {{ text }}</user>`
72
+ }],
73
+ versionName: "feat/email-extractor-v1"
74
+ })
75
+
76
+ // 2. Test
77
+ run_prompt({
78
+ name: "email-extractor",
79
+ parameters: { text: "Contact john@example.com" }
80
+ })
81
+
82
+ // 3. Analyze output → if needs improvement, iterate
83
+ add_prompt({
84
+ prompts: [{
85
+ name: "email-extractor",
86
+ content: "... improved version ..."
87
+ }],
88
+ versionName: "fix/improve-accuracy"
89
+ })
90
+
91
+ // 4. Re-test → repeat until quality threshold met
92
+ ```
93
+
94
+ ### Bulk Testing for Optimization
95
+
96
+ ```bash
97
+ # Test multiple prompts with MCP Inspector
98
+ for prompt in email-extractor sentiment-analyzer; do
99
+ npx @modelcontextprotocol/inspector \
100
+ -e LATITUDE_API_KEY=$KEY \
101
+ -e LATITUDE_PROJECT_ID=$ID \
102
+ --cli npx -y latitude-mcp-server@3.2.0 \
103
+ --method tools/call \
104
+ --tool-name run_prompt \
105
+ --tool-arg name=$prompt \
106
+ --tool-arg 'parameters={"text":"test"}'
107
+ done
108
+
109
+ # Analyze outputs → update weak prompts → re-test
110
+ ```
111
+
112
+ ---
113
+
114
+ ## VERSION NAMING
115
+
116
+ ```javascript
117
+ // Git-style naming (optional)
118
+ versionName: "feat/add-email-extractor"
119
+ versionName: "fix/typo-in-system-message"
120
+ versionName: "refactor/simplify-logic"
121
+ versionName: "perf/reduce-tokens"
122
+
123
+ // If omitted → auto-generates timestamp
124
+ ```
125
+
126
+ ---
127
+
128
+ ## VALIDATION
129
+
130
+ All writes validate BEFORE API calls:
131
+
132
+ ```javascript
133
+ add_prompt({
134
+ prompts: [{
135
+ name: "broken",
136
+ content: `<user><assistant>Nested!</assistant></user>` // ❌
137
+ }]
138
+ })
139
+
140
+ // Error Code: `message-tag-inside-message`
141
+ // Location: Line 1, Column 7
142
+ // Fix: Move the nested tag outside its parent.
143
+ ```
144
+
145
+ ---
146
+
147
+ ## SYNC BEHAVIOR
148
+
149
+ - `push_prompts` - FULL SYNC (deletes extras)
150
+ - `pull_prompts` - FULL SYNC (deletes local first)
151
+ - `add_prompt` - ADDITIVE (never deletes)
152
+
153
+ ---
154
+
155
+ ## QUICK COMMANDS
156
+
157
+ ```
158
+ "Pull all" → pull_prompts
159
+ "Add this file" → add_prompt(filePaths: ["./prompts/x.promptl"])
160
+ "Test prompt" → run_prompt(name: "x", parameters: {...})
161
+ "What params?" → Check run_prompt description (dynamic)
162
+ "What exists?" → Check add_prompt description (dynamic)
163
+ ```
package/README.md CHANGED
@@ -1,22 +1,23 @@
1
1
  # Latitude MCP Server
2
2
 
3
- > Model Context Protocol server for [Latitude.so](https://latitude.so) prompt management
3
+ > **AI-powered prompt management** for [Latitude.so](https://latitude.so) via Model Context Protocol
4
4
 
5
- Manage AI prompts directly from your MCP-compatible AI assistant. Push, pull, run, and version PromptL prompts with 8 focused tools.
5
+ Manage PromptL prompts directly from Claude, Windsurf, or any MCP client. Features **intelligent validation**, **dynamic tool descriptions**, and **git-style versioning**.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/latitude-mcp-server.svg)](https://www.npmjs.com/package/latitude-mcp-server)
8
8
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
9
9
 
10
10
  ---
11
11
 
12
- ## Features
12
+ ## ✨ Key Features
13
13
 
14
- - **8 MCP Tools** - Push, pull, run, and manage prompts
15
- - **52 Documentation Topics** - Comprehensive PromptL syntax guide
16
- - **Semantic Search** - Find docs by natural language query
17
- - **File Operations** - Pull prompts to local `.promptl` files
18
- - **Version Control** - Manage LIVE and draft versions
19
- - **Zero Config** - Works with `LATITUDE_API_KEY` environment variable
14
+ - **🤖 Smart Validation** - Client-side PromptL validation with AST-powered error messages
15
+ - **📋 Dynamic Descriptions** - Tools show available prompts and their parameters automatically
16
+ - **🔄 Full Sync** - Push/pull with automatic conflict resolution
17
+ - **🎯 Atomic Operations** - Validate ALL before pushing ANY (all-or-nothing)
18
+ - **📚 52 Doc Topics** - Comprehensive PromptL syntax guide with semantic search
19
+ - **🏷️ Git-Style Versioning** - Name your changes like commits (feat/add-auth, fix/typo)
20
+ - **⚡ Zero Config** - Just set `LATITUDE_API_KEY` and go
20
21
 
21
22
  ---
22
23
 
@@ -60,86 +61,209 @@ Add to your MCP client config (e.g., Claude Desktop):
60
61
 
61
62
  ---
62
63
 
63
- ## Available Tools
64
+ ## 🛠️ Available Tools (7)
64
65
 
65
- | Tool | Description |
66
- |------|-------------|
67
- | `list_prompts` | List all prompts in LIVE version |
68
- | `get_prompt` | Get full prompt content by name |
69
- | `run_prompt` | Execute a prompt with parameters |
70
- | `push_prompts` | Replace ALL prompts (destructive) |
71
- | `append_prompts` | Add prompts without removing existing |
72
- | `pull_prompts` | Download prompts to local files |
73
- | `replace_prompt` | Replace or create single prompt |
74
- | `docs` | Get documentation (52 topics) |
66
+ | Tool | Type | Description |
67
+ |------|------|-------------|
68
+ | `list_prompts` | Read | List all prompts in LIVE |
69
+ | `get_prompt` | Read | Get full prompt content by name |
70
+ | `run_prompt` | Execute | 🎯 **Dynamic:** Shows all prompts with their parameters |
71
+ | `push_prompts` | Write | 🔄 **FULL SYNC:** Replace ALL prompts (deletes extras) |
72
+ | `pull_prompts` | Read | 🔄 **FULL SYNC:** Download all prompts (deletes local first) |
73
+ | `add_prompt` | Write | 🎯 **Dynamic:** Add/update prompts (shows available prompts) |
74
+ | `docs` | Read | Get documentation (52 topics, semantic search) |
75
+
76
+ ### 🎯 What Makes This Special?
77
+
78
+ **Dynamic Tool Descriptions** - The MCP server updates tool descriptions in real-time:
79
+ - `run_prompt` shows: `"my-prompt" (params: name, email, company)`
80
+ - `add_prompt` shows: `"Available prompts (10): prompt-a, prompt-b, ..."`
81
+
82
+ Your AI assistant sees exactly what prompts exist and what parameters they need!
75
83
 
76
84
  ---
77
85
 
78
- ## Common Workflows
86
+ ## 🚀 Real-World Workflows
79
87
 
80
- ### Pull Prompts to Local Files
88
+ ### Workflow 1: New Project Setup
81
89
 
82
- ```
90
+ ```bash
91
+ # Pull all prompts from LIVE to start local development
83
92
  pull_prompts({ outputDir: "./prompts" })
93
+ # Downloads 10 files to ./prompts/
94
+ # Deletes any existing local .promptl files first (FULL SYNC)
84
95
  ```
85
96
 
86
- Downloads all LIVE prompts to `./prompts/*.promptl` files.
97
+ **What you see:**
98
+ ```
99
+ ✅ Prompts Pulled from LIVE
87
100
 
88
- ### List Available Prompts
101
+ Directory: /Users/you/project/prompts
102
+ Deleted: 0 existing files
103
+ Written: 10 files
89
104
 
90
- ```
91
- list_prompts()
105
+ Files:
106
+ - cover-letter-generate.promptl
107
+ - sentiment-analyzer.promptl
108
+ ...
109
+
110
+ Tip: Edit files locally, then use `add_prompt` to push changes.
92
111
  ```
93
112
 
94
- Returns all prompt names in your project.
113
+ ### Workflow 2: Add New Prompt (with Dynamic Guidance)
114
+
115
+ ```bash
116
+ # The tool description shows you what prompts already exist!
117
+ add_prompt({
118
+ prompts: [{
119
+ name: "email-writer",
120
+ content: `---
121
+ provider: openai
122
+ model: gpt-4o
123
+ ---
124
+ <user>
125
+ Write email to {{ recipient }} about {{ topic }}
126
+ </user>`
127
+ }],
128
+ versionName: "feat/add-email-writer" # Optional git-style naming
129
+ })
130
+ ```
95
131
 
96
- ### Get Prompt Content
132
+ **Dynamic Description Shows:**
133
+ ```
134
+ Add or update prompt(s) in LIVE without deleting others.
97
135
 
136
+ Available prompts (10): cover-letter-generate, sentiment-analyzer, ...
98
137
  ```
99
- get_prompt({ name: "my-prompt" })
138
+
139
+ **Result:**
100
140
  ```
141
+ ✅ Prompts Added to LIVE
101
142
 
102
- Retrieves full PromptL content including config and messages.
143
+ Summary:
144
+ - Added: 1
145
+ - Updated: 0
103
146
 
104
- ### Run a Prompt
147
+ Added:
148
+ - email-writer
105
149
 
150
+ Current LIVE prompts (11): cover-letter-generate, ..., email-writer
106
151
  ```
107
- run_prompt({
108
- name: "my-prompt",
109
- parameters: { user_name: "Alice" }
152
+
153
+ ### Workflow 3: Run Prompt (with Parameter Discovery)
154
+
155
+ ```bash
156
+ # The tool description shows you what parameters each prompt needs!
157
+ run_prompt({
158
+ name: "email-writer",
159
+ parameters: {
160
+ recipient: "Alice",
161
+ topic: "project update"
162
+ }
110
163
  })
111
164
  ```
112
165
 
113
- Executes the prompt and returns AI response.
166
+ **Dynamic Description Shows:**
167
+ ```
168
+ Execute a prompt with parameters.
114
169
 
115
- ### Update a Prompt
170
+ Available prompts (11):
171
+ - cover-letter-generate (params: job_details, career_patterns, company_name)
172
+ - email-writer (params: recipient, topic)
173
+ - sentiment-analyzer (no params)
174
+ ...
175
+ ```
116
176
 
177
+ **Result:**
117
178
  ```
118
- replace_prompt({
119
- name: "greeting",
120
- content: `---
121
- provider: OpenAI
122
- model: gpt-4o
179
+ ✅ Prompt Executed
180
+
181
+ Prompt: email-writer
182
+
183
+ Parameters:
184
+ {
185
+ "recipient": "Alice",
186
+ "topic": "project update"
187
+ }
188
+
189
+ Response:
190
+ Subject: Project Update
191
+
192
+ Dear Alice,
193
+
194
+ I wanted to share an update on our project...
195
+
196
+ Tokens: 245 total
197
+ ```
198
+
199
+ ### Workflow 4: Validation Catches Errors
200
+
201
+ ```bash
202
+ # Try to add a prompt with nested tags (invalid PromptL)
203
+ add_prompt({
204
+ prompts: [{
205
+ name: "broken",
206
+ content: `---
207
+ model: gpt-4
123
208
  ---
124
- Hello {{ user_name }}!`
209
+ <user><assistant>Nested!</assistant></user>`
210
+ }]
125
211
  })
126
212
  ```
127
213
 
128
- Creates or updates a single prompt in LIVE.
214
+ **Validation Error (Before ANY API Call):**
215
+ ```
216
+ ❌ Validation Failed - No Changes Made
129
217
 
130
- ### Get Documentation
218
+ 1 prompt(s) have errors. Fix all errors before pushing.
131
219
 
220
+ ### broken
221
+ Error Code: `message-tag-inside-message`
222
+ Error: Message tags cannot be inside of another message
223
+ Root Cause: Message/role tags (<system>, <user>, <assistant>, <tool>) cannot be nested.
224
+ Location: Line 4, Column 7
225
+ Code Context:
132
226
  ```
133
- docs({ action: "help" }) # Overview
134
- docs({ action: "find", query: "variables" }) # Search
135
- docs({ action: "get", topic: "chains" }) # Specific topic
227
+ 2: model: gpt-4
228
+ 3: ---
229
+ 4: <user><assistant>Nested!</assistant></user>
230
+
231
+ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136
232
  ```
233
+ Fix: Move the nested tag outside its parent. Use code block (```yaml) for examples.
137
234
 
138
- Access comprehensive PromptL documentation (52 topics).
235
+ Action Required: Fix the errors above, then retry.
236
+ ```
237
+
238
+ ### Workflow 5: Full Sync (Initialization)
239
+
240
+ ```bash
241
+ # Push local prompts to LIVE - deletes remote prompts not in your list
242
+ push_prompts({
243
+ filePaths: [
244
+ "./prompts/prompt-a.promptl",
245
+ "./prompts/prompt-b.promptl",
246
+ "./prompts/prompt-c.promptl"
247
+ ],
248
+ versionName: "feat/initial-prompts" # Optional
249
+ })
250
+ ```
251
+
252
+ **Result:**
253
+ ```
254
+ ✅ Prompts Pushed to LIVE
255
+
256
+ Summary:
257
+ - Added: 3
258
+ - Modified: 0
259
+ - Deleted: 8 # Removed old prompts not in your list
260
+
261
+ Current LIVE prompts (3): prompt-a, prompt-b, prompt-c
262
+ ```
139
263
 
140
264
  ---
141
265
 
142
- ## Documentation Topics (52)
266
+ ## 📚 Documentation Topics (52)
143
267
 
144
268
  ### Core Syntax (12)
145
269
  `overview`, `structure`, `variables`, `conditionals`, `loops`, `references`, `tools`, `chains`, `agents`, `techniques`, `agent-patterns`, `mocking`
@@ -164,24 +288,48 @@ Access comprehensive PromptL documentation (52 topics).
164
288
 
165
289
  ---
166
290
 
167
- ## Development
291
+ ## 🛠️ Development
168
292
 
169
293
  ### Build
170
294
 
171
295
  ```bash
172
- npm run build
296
+ npm run build # Compiles TypeScript to dist/
173
297
  ```
174
298
 
175
- Compiles TypeScript to `dist/`.
176
-
177
- ### Test with MCP Inspector
299
+ ### Testing with MCP Inspector
178
300
 
179
301
  ```bash
302
+ # List all tools
180
303
  npx @modelcontextprotocol/inspector \
181
304
  -e LATITUDE_API_KEY=your-key \
182
305
  -e LATITUDE_PROJECT_ID=your-id \
183
306
  --cli node dist/index.js \
184
307
  --method tools/list
308
+
309
+ # Test list_prompts
310
+ npx @modelcontextprotocol/inspector \
311
+ -e LATITUDE_API_KEY=your-key \
312
+ -e LATITUDE_PROJECT_ID=your-id \
313
+ --cli node dist/index.js \
314
+ --method tools/call \
315
+ --tool-name list_prompts
316
+
317
+ # Test add_prompt with file
318
+ npx @modelcontextprotocol/inspector \
319
+ -e LATITUDE_API_KEY=your-key \
320
+ -e LATITUDE_PROJECT_ID=your-id \
321
+ --cli node dist/index.js \
322
+ --method tools/call \
323
+ --tool-name add_prompt \
324
+ --tool-arg 'filePaths=["./prompts/test.promptl"]'
325
+
326
+ # Test from npm package
327
+ npx @modelcontextprotocol/inspector \
328
+ -e LATITUDE_API_KEY=your-key \
329
+ -e LATITUDE_PROJECT_ID=your-id \
330
+ --cli npx -y latitude-mcp-server@3.1.0 \
331
+ --method tools/call \
332
+ --tool-name list_prompts
185
333
  ```
186
334
 
187
335
  ### Local Development
@@ -193,6 +341,10 @@ node dist/index.js
193
341
 
194
342
  # With environment variables
195
343
  LATITUDE_API_KEY=xxx LATITUDE_PROJECT_ID=yyy node dist/index.js
344
+
345
+ # Watch mode (requires nodemon)
346
+ npm install -g nodemon
347
+ nodemon --watch src --exec "npm run build && node dist/index.js"
196
348
  ```
197
349
 
198
350
  ---
@@ -281,63 +433,103 @@ Use `docs({ action: "get", topic: "overview" })` for complete guide.
281
433
 
282
434
  ---
283
435
 
284
- ## API Reference
436
+ ## 📖 Tool Reference
285
437
 
286
438
  ### list_prompts()
287
439
 
288
440
  List all prompts in LIVE version.
289
441
 
290
- **Returns:** Array of prompt names
442
+ **Returns:** Array of prompt names with project ID
443
+
444
+ **Example:**
445
+ ```javascript
446
+ list_prompts()
447
+ // Returns: cover-letter-generate, sentiment-analyzer, email-writer (10 total)
448
+ ```
449
+
450
+ ---
291
451
 
292
452
  ### get_prompt({ name })
293
453
 
294
- Get full prompt content.
454
+ Get full prompt content by name.
295
455
 
296
456
  **Parameters:**
297
457
  - `name` (string) - Prompt name
298
458
 
299
- **Returns:** Prompt content with config and messages
459
+ **Returns:** Full PromptL content with config and messages
460
+
461
+ **Example:**
462
+ ```javascript
463
+ get_prompt({ name: "email-writer" })
464
+ // Returns full .promptl content
465
+ ```
466
+
467
+ ---
300
468
 
301
469
  ### run_prompt({ name, parameters })
302
470
 
303
- Execute a prompt.
471
+ 🎯 **Dynamic:** Execute a prompt. Tool description shows all prompts with their parameters!
304
472
 
305
473
  **Parameters:**
306
474
  - `name` (string) - Prompt name
307
- - `parameters` (object) - Input parameters
475
+ - `parameters` (object, optional) - Input parameters
308
476
 
309
- **Returns:** AI response
477
+ **Returns:** AI response with token usage
310
478
 
311
- ### pull_prompts({ outputDir? })
479
+ **Dynamic Description:**
480
+ ```
481
+ Available prompts (10):
482
+ - email-writer (params: recipient, topic)
483
+ - sentiment-analyzer (no params)
484
+ - cover-letter-generate (params: job_details, career_patterns, company_name)
485
+ ```
312
486
 
313
- Download prompts to local files.
487
+ **Example:**
488
+ ```javascript
489
+ run_prompt({
490
+ name: "email-writer",
491
+ parameters: { recipient: "Alice", topic: "update" }
492
+ })
493
+ ```
314
494
 
315
- **Parameters:**
316
- - `outputDir` (string, optional) - Output directory (default: `./prompts`)
495
+ ---
317
496
 
318
- **Returns:** List of created files
497
+ ### add_prompt({ prompts?, filePaths?, versionName? })
319
498
 
320
- ### replace_prompt({ name?, content?, filePath? })
499
+ 🎯 **Dynamic:** Add or update prompts without deleting others. Tool description shows available prompts!
321
500
 
322
- Replace or create a single prompt. **Shows all available prompts in description.**
501
+ **Behavior:** If prompt exists overwrites. If new adds. Never deletes other prompts.
323
502
 
324
- **Parameters (choose one approach):**
503
+ **Parameters (choose one):**
325
504
 
326
505
  **Option A - Direct content:**
327
- - `name` (string) - Prompt name
328
- - `content` (string) - Full PromptL content
506
+ - `prompts` (array) - Array of `{ name, content }`
507
+
508
+ **Option B - From files:**
509
+ - `filePaths` (array) - Array of paths to `.promptl` files
510
+
511
+ **Common:**
512
+ - `versionName` (string, optional) - Git-style name like `feat/add-auth` or `fix/typo`
513
+
514
+ **Returns:** Summary of added/updated prompts
329
515
 
330
- **Option B - From file:**
331
- - `filePath` (string) - Path to `.promptl` file (name auto-detected from filename)
332
- - `name` (string, optional) - Override name from filename
516
+ **Example:**
517
+ ```javascript
518
+ add_prompt({
519
+ filePaths: ["./prompts/new-prompt.promptl"],
520
+ versionName: "feat/add-new-prompt"
521
+ })
522
+ ```
333
523
 
334
- **Returns:** Success confirmation + updated list of all LIVE prompts
524
+ ---
525
+
526
+ ### push_prompts({ prompts?, filePaths?, versionName? })
335
527
 
336
- ### append_prompts({ prompts?, filePaths?, overwrite? })
528
+ 🔄 **FULL SYNC:** Replace ALL prompts in LIVE. Deletes remote prompts not in your list.
337
529
 
338
- Add prompts without removing existing ones.
530
+ **Use for:** Initial setup, complete sync, resetting LIVE to match local.
339
531
 
340
- **Parameters (choose one approach):**
532
+ **Parameters (choose one):**
341
533
 
342
534
  **Option A - Direct content:**
343
535
  - `prompts` (array) - Array of `{ name, content }`
@@ -346,106 +538,196 @@ Add prompts without removing existing ones.
346
538
  - `filePaths` (array) - Array of paths to `.promptl` files
347
539
 
348
540
  **Common:**
349
- - `overwrite` (boolean, optional) - Overwrite existing (default: false)
541
+ - `versionName` (string, optional) - Git-style name like `feat/initial-setup`
350
542
 
351
- **Returns:** Success confirmation + updated list of all LIVE prompts
543
+ **Returns:** Summary of added/modified/deleted prompts
352
544
 
353
- ### push_prompts({ prompts?, filePaths? })
545
+ **Example:**
546
+ ```javascript
547
+ push_prompts({
548
+ filePaths: ["./prompts/prompt-a.promptl", "./prompts/prompt-b.promptl"],
549
+ versionName: "feat/complete-rewrite"
550
+ })
551
+ ```
354
552
 
355
- ⚠️ **Destructive:** Replaces ALL prompts in LIVE.
553
+ ---
356
554
 
357
- **Parameters (choose one approach):**
555
+ ### pull_prompts({ outputDir? })
358
556
 
359
- **Option A - Direct content:**
360
- - `prompts` (array) - Array of `{ name, content }`
557
+ 🔄 **FULL SYNC:** Download all prompts from LIVE. Deletes existing local `.promptl` files first.
361
558
 
362
- **Option B - From files:**
363
- - `filePaths` (array) - Array of paths to `.promptl` files
559
+ **Use for:** Initial clone, resetting local to match LIVE.
364
560
 
365
- **Returns:** Success confirmation + updated list of all LIVE prompts
561
+ **Parameters:**
562
+ - `outputDir` (string, optional) - Output directory (default: `./prompts`)
563
+
564
+ **Returns:** List of downloaded files
565
+
566
+ **Example:**
567
+ ```javascript
568
+ pull_prompts({ outputDir: "./my-prompts" })
569
+ ```
570
+
571
+ ---
366
572
 
367
573
  ### docs({ action, topic?, query? })
368
574
 
369
- Get documentation.
575
+ Access comprehensive PromptL documentation (52 topics).
370
576
 
371
577
  **Parameters:**
372
- - `action` (string) - `"help"`, `"get"`, or `"find"`
578
+ - `action` (string) - `"help"` (overview), `"get"` (topic), or `"find"` (search)
373
579
  - `topic` (string, optional) - Topic name for `"get"`
374
580
  - `query` (string, optional) - Search query for `"find"`
375
581
 
376
582
  **Returns:** Documentation content
377
583
 
584
+ **Examples:**
585
+ ```javascript
586
+ docs({ action: "help" }) // Overview
587
+ docs({ action: "find", query: "json output" }) // Semantic search
588
+ docs({ action: "get", topic: "chains" }) // Specific topic
589
+ ```
590
+
378
591
  ---
379
592
 
380
- ## Examples
593
+ ## ✅ Validation Features
381
594
 
382
- ### Example 1: Pull → Edit → Push Workflow (Recommended)
595
+ ### Client-Side Validation with AST
383
596
 
384
- ```javascript
385
- // 1. Pull all prompts to local files
386
- pull_prompts({ outputDir: "./prompts" })
387
- // Creates: ./prompts/my-prompt.promptl, ./prompts/other-prompt.promptl, etc.
597
+ All write operations (`add_prompt`, `push_prompts`) validate prompts **before** making API calls using the official `promptl-ai` library.
388
598
 
389
- // 2. Edit files locally in your IDE
599
+ **Benefits:**
600
+ - ⚡ **Fast feedback** - No wasted API calls
601
+ - 🎯 **Precise errors** - Exact line and column numbers
602
+ - 📝 **Code frames** - See surrounding context with `^~~~` pointer
603
+ - 🤖 **LLM-actionable** - Errors include root cause and fix suggestions
390
604
 
391
- // 3. Push single file back (name auto-detected from filename)
392
- replace_prompt({ filePath: "./prompts/my-prompt.promptl" })
605
+ ### Atomic Operations
393
606
 
394
- // 4. Or push multiple files
395
- append_prompts({
396
- filePaths: ["./prompts/prompt-a.promptl", "./prompts/prompt-b.promptl"],
397
- overwrite: true
607
+ **Validate ALL, push ALL or NOTHING:**
608
+
609
+ ```javascript
610
+ // Trying to push 10 prompts, but 1 has an error
611
+ add_prompt({
612
+ filePaths: [
613
+ "./prompts/valid-1.promptl",
614
+ "./prompts/valid-2.promptl",
615
+ "./prompts/BROKEN.promptl", // Has nested tags
616
+ // ... 7 more valid prompts
617
+ ]
398
618
  })
619
+
620
+ // Result: NOTHING is pushed
621
+ // Error shows exactly what's wrong in BROKEN.promptl
622
+ // Fix the error, retry → all 10 push successfully
399
623
  ```
400
624
 
401
- ### Example 2: Create New Prompt
625
+ ### Error Message Example
402
626
 
403
- ```javascript
404
- replace_prompt({
405
- name: "sentiment-analyzer",
406
- content: `---
407
- provider: OpenAI
408
- model: gpt-4o
409
- temperature: 0
410
- schema:
411
- type: object
412
- properties:
413
- sentiment:
414
- type: string
415
- enum: [positive, negative, neutral]
416
- required: [sentiment]
417
- ---
418
- <system>
419
- You are a sentiment analysis expert.
420
- </system>
627
+ ```
628
+ ❌ Validation Failed - No Changes Made
421
629
 
422
- <user>
423
- Analyze: {{ text }}
424
- </user>`
425
- })
630
+ 1 prompt(s) have errors.
631
+
632
+ ### my-prompt
633
+ Error Code: `message-tag-inside-message`
634
+ Error: Message tags cannot be inside of another message
635
+ Root Cause: Message/role tags cannot be nested inside each other.
636
+ Location: Line 107, Column 1
637
+ Code Context:
426
638
  ```
639
+ 105: ## EXAMPLES
640
+ 106:
641
+ 107: <assistant>
427
642
 
428
- ### Example 3: Run with Parameters
643
+ ^~~~~~~~~~~~
644
+ 108: questions:
645
+ 109: - id: q1
646
+ ```
647
+ Fix: Move the nested tag outside its parent. Use code block (```yaml) instead.
648
+ ```
649
+
650
+ ### Supported Error Types
651
+
652
+ - `message-tag-inside-message` - Nested role tags
653
+ - `content-tag-inside-content` - Nested content tags
654
+ - `config-not-found` - Missing YAML frontmatter
655
+ - `invalid-config` - Malformed YAML
656
+ - `unclosed-block` - Missing closing tag
657
+ - `variable-not-defined` - Undefined variable
658
+ - `invalid-tool-call-placement` - Tool call outside `<assistant>`
659
+ - ...and more from official `promptl-ai` compiler
660
+
661
+ ---
429
662
 
663
+ ## 🔄 Migration Guide (v2 → v3)
664
+
665
+ ### Tool Changes
666
+
667
+ | Old Tool (v2) | New Tool (v3) | Notes |
668
+ |---------------|---------------|-------|
669
+ | `append_prompts` | `add_prompt` | Always overwrites if exists (no `overwrite` param needed) |
670
+ | `replace_prompt` | `add_prompt` | Same behavior, unified tool |
671
+
672
+ **Migration:**
430
673
  ```javascript
431
- const result = await run_prompt({
432
- name: "sentiment-analyzer",
433
- parameters: { text: "I love this product!" }
434
- })
674
+ // OLD (v2)
675
+ append_prompts({ filePaths: [...], overwrite: true })
676
+ replace_prompt({ filePath: "./prompt.promptl" })
435
677
 
436
- // Returns: { sentiment: "positive" }
678
+ // NEW (v3)
679
+ add_prompt({ filePaths: [...] }) // Always overwrites if exists
437
680
  ```
438
681
 
439
- ### Example 4: Search Documentation
682
+ ---
440
683
 
441
- ```javascript
442
- // Find topics about JSON
443
- docs({ action: "find", query: "json output" })
684
+ ## 🔧 Troubleshooting
444
685
 
445
- // Get specific topic
446
- docs({ action: "get", topic: "config-json-output" })
686
+ ### "Validation Failed" Errors
687
+
688
+ **Problem:** Prompt fails with nested tag error
689
+
690
+ **Solution:** The error shows exact location with code frame:
691
+ ```
692
+ Error Code: `message-tag-inside-message`
693
+ Location: Line 4, Column 7
694
+ Code Context:
695
+ 4: <user><assistant>Nested!</assistant></user>
696
+ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
697
+ Fix: Move the nested tag outside its parent.
447
698
  ```
448
699
 
700
+ Follow the fix suggestion - errors are LLM-actionable!
701
+
702
+ ### "No Changes Made" After Push
703
+
704
+ **Problem:** `push_prompts` reports no changes
705
+
706
+ **Cause:** All prompts are already up to date (content matches LIVE)
707
+
708
+ **Solution:** This is normal - no action needed
709
+
710
+ ### Version Naming Best Practices
711
+
712
+ **Good:**
713
+ - `feat/add-sentiment-analyzer`
714
+ - `fix/typo-in-greeting`
715
+ - `refactor/simplify-prompts`
716
+ - `docs/update-examples`
717
+
718
+ **Avoid:**
719
+ - `test` (too vague)
720
+ - `update` (what was updated?)
721
+ - `v1.2.3` (use semantic versioning elsewhere)
722
+
723
+ ### Dynamic Descriptions Not Updating
724
+
725
+ **Problem:** Tool descriptions show old prompt list
726
+
727
+ **Cause:** Cache not refreshed (30s TTL)
728
+
729
+ **Solution:** Wait 30 seconds or restart MCP server
730
+
449
731
  ---
450
732
 
451
733
  ## Contributing
package/dist/api.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * - POST /projects/:id/versions/:uuid/documents/create-or-update - create/update document
12
12
  * - POST /projects/:id/versions/:uuid/documents/run - run document
13
13
  */
14
- import type { LatitudeError, Version, Document, DocumentChange, DeployResult, RunResult } from './types.js';
14
+ import type { LatitudeError, Document, DocumentChange, DeployResult, RunResult } from './types.js';
15
15
  export declare class LatitudeApiError extends Error {
16
16
  readonly errorCode: string;
17
17
  readonly statusCode: number;
@@ -32,14 +32,8 @@ export declare class LatitudeApiError extends Error {
32
32
  * Get project ID from config
33
33
  */
34
34
  export declare function getProjectId(): string;
35
- export declare function listVersions(): Promise<Version[]>;
36
- export declare function getVersion(versionUuid: string): Promise<Version>;
37
- export declare function createVersion(name: string): Promise<Version>;
38
- export declare function publishVersion(versionUuid: string, title?: string): Promise<Version>;
39
35
  export declare function listDocuments(versionUuid?: string): Promise<Document[]>;
40
36
  export declare function getDocument(path: string, versionUuid?: string): Promise<Document>;
41
- export declare function createOrUpdateDocument(versionUuid: string, path: string, content: string, force?: boolean): Promise<Document>;
42
- export declare function deleteDocument(versionUuid: string, path: string): Promise<void>;
43
37
  /**
44
38
  * Push response from the API
45
39
  */
@@ -88,5 +82,4 @@ export declare function validatePromptLContent(content: string, path: string): P
88
82
  * This is the same workflow the CLI uses, ensuring proper validation.
89
83
  */
90
84
  export declare function deployToLive(changes: DocumentChange[], _versionName?: string): Promise<DeployResult>;
91
- export declare function getPromptNames(): Promise<string[]>;
92
85
  export {};
package/dist/api.js CHANGED
@@ -15,20 +15,13 @@
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.LatitudeApiError = void 0;
17
17
  exports.getProjectId = getProjectId;
18
- exports.listVersions = listVersions;
19
- exports.getVersion = getVersion;
20
- exports.createVersion = createVersion;
21
- exports.publishVersion = publishVersion;
22
18
  exports.listDocuments = listDocuments;
23
19
  exports.getDocument = getDocument;
24
- exports.createOrUpdateDocument = createOrUpdateDocument;
25
- exports.deleteDocument = deleteDocument;
26
20
  exports.pushChanges = pushChanges;
27
21
  exports.computeDiff = computeDiff;
28
22
  exports.runDocument = runDocument;
29
23
  exports.validatePromptLContent = validatePromptLContent;
30
24
  exports.deployToLive = deployToLive;
31
- exports.getPromptNames = getPromptNames;
32
25
  const logger_util_js_1 = require("./utils/logger.util.js");
33
26
  const config_util_js_1 = require("./utils/config.util.js");
34
27
  const promptl_ai_1 = require("promptl-ai");
@@ -266,14 +259,6 @@ exports.LatitudeApiError = LatitudeApiError;
266
259
  function getProjectId() {
267
260
  return getConfig().projectId;
268
261
  }
269
- async function listVersions() {
270
- const projectId = getProjectId();
271
- return request(`/projects/${projectId}/versions`);
272
- }
273
- async function getVersion(versionUuid) {
274
- const projectId = getProjectId();
275
- return request(`/projects/${projectId}/versions/${versionUuid}`);
276
- }
277
262
  async function createVersion(name) {
278
263
  const projectId = getProjectId();
279
264
  return request(`/projects/${projectId}/versions`, {
@@ -298,19 +283,6 @@ async function getDocument(path, versionUuid = 'live') {
298
283
  const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
299
284
  return request(`/projects/${projectId}/versions/${versionUuid}/documents/${normalizedPath}`);
300
285
  }
301
- async function createOrUpdateDocument(versionUuid, path, content, force = false) {
302
- const projectId = getProjectId();
303
- logger.debug(`Creating/updating document: ${path} (${content.length} chars, force=${force})`);
304
- return request(`/projects/${projectId}/versions/${versionUuid}/documents/create-or-update`, {
305
- method: 'POST',
306
- body: { path, prompt: content, force },
307
- });
308
- }
309
- async function deleteDocument(versionUuid, path) {
310
- const projectId = getProjectId();
311
- const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
312
- await request(`/projects/${projectId}/versions/${versionUuid}/documents/${normalizedPath}`, { method: 'DELETE' });
313
- }
314
286
  /**
315
287
  * Push changes to a version in a single batch
316
288
  * This is the CLI-style push that sends all changes at once
@@ -740,13 +712,3 @@ async function deployToLive(changes, _versionName) {
740
712
  throw publishError;
741
713
  }
742
714
  }
743
- async function getPromptNames() {
744
- try {
745
- const docs = await listDocuments('live');
746
- return docs.map((doc) => doc.path);
747
- }
748
- catch (error) {
749
- logger.warn('Failed to fetch prompt names', error);
750
- return [];
751
- }
752
- }
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ async function main() {
20
20
  (0, server_js_1.setupGracefulShutdown)();
21
21
  // Log startup info
22
22
  logger.info('='.repeat(50));
23
- logger.info('Latitude MCP Server v2.0.0');
23
+ logger.info('Latitude MCP Server v3.1.0');
24
24
  logger.info('='.repeat(50));
25
25
  // Validate required environment variables
26
26
  const apiKey = config_util_js_1.config.get('LATITUDE_API_KEY');
package/dist/server.js CHANGED
@@ -17,7 +17,7 @@ const logger = logger_util_js_1.Logger.forContext('server.ts');
17
17
  // Server Configuration
18
18
  // ============================================================================
19
19
  const SERVER_NAME = 'latitude-mcp-server';
20
- const SERVER_VERSION = '2.0.0';
20
+ const SERVER_VERSION = '3.1.0';
21
21
  // ============================================================================
22
22
  // Server Instance
23
23
  // ============================================================================
package/dist/tools.js CHANGED
@@ -292,6 +292,10 @@ const PushPromptsSchema = zod_1.z.object({
292
292
  .array(zod_1.z.string())
293
293
  .optional()
294
294
  .describe('File paths to .promptl files - FULL SYNC: deletes remote prompts not in this list'),
295
+ versionName: zod_1.z
296
+ .string()
297
+ .optional()
298
+ .describe('Optional version name (like git branch: feat/add-auth, fix/typo). If omitted, auto-generates timestamp.'),
295
299
  });
296
300
  async function handlePushPrompts(args) {
297
301
  try {
@@ -334,7 +338,7 @@ async function handlePushPrompts(args) {
334
338
  }
335
339
  // Push all changes in one batch
336
340
  try {
337
- const result = await (0, api_js_1.deployToLive)(changes, 'push');
341
+ const result = await (0, api_js_1.deployToLive)(changes, args.versionName || 'push');
338
342
  // Force refresh cache after mutations
339
343
  const newNames = await forceRefreshAndGetNames();
340
344
  let content = `**Summary:**\n`;
@@ -381,6 +385,10 @@ const AddPromptSchema = zod_1.z.object({
381
385
  .array(zod_1.z.string())
382
386
  .optional()
383
387
  .describe('File paths to .promptl files - overwrites if exists, adds if new'),
388
+ versionName: zod_1.z
389
+ .string()
390
+ .optional()
391
+ .describe('Optional version name (like git commit: feat/add-auth, fix/typo). Describes what changed.'),
384
392
  });
385
393
  async function handleAddPrompt(args) {
386
394
  try {
@@ -444,7 +452,7 @@ async function handleAddPrompt(args) {
444
452
  }
445
453
  // Push all changes in one batch
446
454
  try {
447
- const result = await (0, api_js_1.deployToLive)(changes, 'add');
455
+ const result = await (0, api_js_1.deployToLive)(changes, args.versionName || 'add');
448
456
  // Force refresh cache after mutations
449
457
  const newNames = await forceRefreshAndGetNames();
450
458
  let content = `**Summary:**\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latitude-mcp-server",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "Simplified MCP server for Latitude.so prompt management - 8 focused tools for push, pull, run, and manage prompts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",