@simple-product/mcp 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import chalk from "chalk";
6
6
  import ora from "ora";
7
7
  import open from "open";
@@ -12,7 +12,7 @@ import * as crypto from "crypto";
12
12
  // ============================================
13
13
  // Configuration
14
14
  // ============================================
15
- const PRODUCTION_URL = "https://simple-product.app";
15
+ const PRODUCTION_URL = "https://simpleproduct.dev";
16
16
  const DEV_URL = "http://localhost:3000";
17
17
  const CONFIG_DIR = path.join(os.homedir(), ".simple-product");
18
18
  const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
@@ -354,8 +354,418 @@ async function runServer() {
354
354
  }, {
355
355
  capabilities: {
356
356
  tools: {},
357
+ resources: {},
357
358
  },
358
359
  });
360
+ // ============================================
361
+ // Documentation Resources
362
+ // ============================================
363
+ const resources = [
364
+ {
365
+ uri: "docs://simple-product/feedback-setup",
366
+ name: "Feedback Collection Setup Guide",
367
+ description: "Complete guide for implementing customer feedback collection in your app",
368
+ mimeType: "text/markdown",
369
+ },
370
+ {
371
+ uri: "docs://simple-product/api-reference",
372
+ name: "API Reference",
373
+ description: "Full API reference for all Simple Product endpoints",
374
+ mimeType: "text/markdown",
375
+ },
376
+ {
377
+ uri: "docs://simple-product/people-api",
378
+ name: "People & Organizations API",
379
+ description: "Guide for tracking customers and organizations",
380
+ mimeType: "text/markdown",
381
+ },
382
+ ];
383
+ const resourceContents = {
384
+ "docs://simple-product/feedback-setup": `# Feedback Collection Setup Guide
385
+
386
+ Simple Product makes it easy to collect customer feedback from your app.
387
+
388
+ ## Quick Start Options
389
+
390
+ ### Option 1: HTML Form (Simplest)
391
+
392
+ Add this form anywhere in your app. No JavaScript required:
393
+
394
+ \`\`\`html
395
+ <form action="${baseUrl}/api/v1/feedback?key=YOUR_API_KEY&redirect=https://yoursite.com/thanks" method="POST">
396
+ <input type="hidden" name="title" value="Feedback" />
397
+ <input name="email" placeholder="Email (optional)" />
398
+ <input name="name" placeholder="Name (optional)" />
399
+ <textarea name="content" placeholder="Your feedback..." required></textarea>
400
+ <button type="submit">Send Feedback</button>
401
+ </form>
402
+ \`\`\`
403
+
404
+ After submission, users are redirected to your URL with \`?success=true\` or \`?error=message\`.
405
+
406
+ ### Option 2: JavaScript/Fetch
407
+
408
+ \`\`\`javascript
409
+ async function submitFeedback(feedback) {
410
+ const response = await fetch('${baseUrl}/api/v1/feedback', {
411
+ method: 'POST',
412
+ headers: {
413
+ 'Authorization': 'Bearer YOUR_API_KEY',
414
+ 'Content-Type': 'application/json',
415
+ },
416
+ body: JSON.stringify({
417
+ title: feedback.title || 'Feedback',
418
+ content: feedback.content,
419
+ email: feedback.email, // Optional: links to customer record
420
+ name: feedback.name, // Optional
421
+ source: 'web-widget', // Optional: track where feedback came from
422
+ metadata: { // Optional: any custom data
423
+ page: window.location.pathname,
424
+ userAgent: navigator.userAgent,
425
+ }
426
+ }),
427
+ });
428
+ return response.json();
429
+ }
430
+ \`\`\`
431
+
432
+ ### Option 3: React Component
433
+
434
+ \`\`\`tsx
435
+ import { useState } from 'react';
436
+
437
+ function FeedbackWidget({ apiKey }: { apiKey: string }) {
438
+ const [content, setContent] = useState('');
439
+ const [email, setEmail] = useState('');
440
+ const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
441
+
442
+ const handleSubmit = async (e: React.FormEvent) => {
443
+ e.preventDefault();
444
+ setStatus('sending');
445
+
446
+ try {
447
+ const res = await fetch('${baseUrl}/api/v1/feedback', {
448
+ method: 'POST',
449
+ headers: {
450
+ 'Authorization': \`Bearer \${apiKey}\`,
451
+ 'Content-Type': 'application/json',
452
+ },
453
+ body: JSON.stringify({
454
+ title: 'Feedback',
455
+ content,
456
+ email: email || undefined,
457
+ source: 'react-widget',
458
+ }),
459
+ });
460
+
461
+ if (res.ok) {
462
+ setStatus('sent');
463
+ setContent('');
464
+ setEmail('');
465
+ } else {
466
+ setStatus('error');
467
+ }
468
+ } catch {
469
+ setStatus('error');
470
+ }
471
+ };
472
+
473
+ if (status === 'sent') {
474
+ return <div>Thanks for your feedback!</div>;
475
+ }
476
+
477
+ return (
478
+ <form onSubmit={handleSubmit}>
479
+ <input
480
+ type="email"
481
+ placeholder="Email (optional)"
482
+ value={email}
483
+ onChange={(e) => setEmail(e.target.value)}
484
+ />
485
+ <textarea
486
+ placeholder="Your feedback..."
487
+ value={content}
488
+ onChange={(e) => setContent(e.target.value)}
489
+ required
490
+ />
491
+ <button type="submit" disabled={status === 'sending'}>
492
+ {status === 'sending' ? 'Sending...' : 'Send Feedback'}
493
+ </button>
494
+ {status === 'error' && <p>Something went wrong. Please try again.</p>}
495
+ </form>
496
+ );
497
+ }
498
+ \`\`\`
499
+
500
+ ## API Reference
501
+
502
+ ### POST /api/v1/feedback
503
+
504
+ **Headers:**
505
+ - \`Authorization: Bearer YOUR_API_KEY\` (required for JSON requests)
506
+ - \`Content-Type: application/json\`
507
+
508
+ **Body:**
509
+ | Field | Type | Required | Description |
510
+ |-------|------|----------|-------------|
511
+ | title | string | Yes | Feedback title/subject |
512
+ | content | string | Yes | The feedback message |
513
+ | email | string | No | Customer email (creates/links customer record) |
514
+ | name | string | No | Customer name |
515
+ | externalId | string | No | Your user ID for identity matching |
516
+ | source | string | No | Where feedback came from (e.g., "widget", "nps") |
517
+ | metadata | object | No | Any custom JSON data |
518
+
519
+ **Response:**
520
+ \`\`\`json
521
+ {
522
+ "data": {
523
+ "id": "abc123",
524
+ "title": "Feedback",
525
+ "content": "...",
526
+ "person": { "id": "...", "name": "John", "email": "john@example.com" },
527
+ "createdAt": 1234567890
528
+ }
529
+ }
530
+ \`\`\`
531
+
532
+ ## Getting Your API Key
533
+
534
+ 1. Go to Settings → API Keys in your Simple Product workspace
535
+ 2. Create a new API key
536
+ 3. Copy the key and store it securely (it won't be shown again)
537
+
538
+ ## Tips
539
+
540
+ - **Link feedback to customers**: Include \`email\` to automatically create/link customer records
541
+ - **Track sources**: Use the \`source\` field to see where feedback comes from
542
+ - **Add context**: Use \`metadata\` to include page URL, user agent, feature flags, etc.
543
+ `,
544
+ "docs://simple-product/api-reference": `# Simple Product API Reference
545
+
546
+ Base URL: \`${baseUrl}/api/v1\`
547
+
548
+ ## Authentication
549
+
550
+ All API requests require authentication via API key:
551
+
552
+ \`\`\`
553
+ Authorization: Bearer YOUR_API_KEY
554
+ \`\`\`
555
+
556
+ Get your API key from Settings → API Keys in your workspace.
557
+
558
+ ## Endpoints
559
+
560
+ ### Feedback
561
+
562
+ #### POST /api/v1/feedback
563
+ Submit customer feedback.
564
+
565
+ **Body:**
566
+ \`\`\`json
567
+ {
568
+ "title": "Feature request",
569
+ "content": "I would love to see...",
570
+ "email": "customer@example.com",
571
+ "name": "John Doe",
572
+ "source": "widget",
573
+ "metadata": { "page": "/settings" }
574
+ }
575
+ \`\`\`
576
+
577
+ ### People (Customers)
578
+
579
+ #### POST /api/v1/people
580
+ Create or update a customer record.
581
+
582
+ **Body:**
583
+ \`\`\`json
584
+ {
585
+ "email": "customer@example.com",
586
+ "name": "John Doe",
587
+ "externalId": "user_123",
588
+ "properties": {
589
+ "plan": "pro",
590
+ "company": "Acme Inc"
591
+ }
592
+ }
593
+ \`\`\`
594
+
595
+ #### GET /api/v1/people?email=customer@example.com
596
+ Look up a customer by email.
597
+
598
+ #### GET /api/v1/people/:id
599
+ Get a customer by ID.
600
+
601
+ ### Organizations
602
+
603
+ #### POST /api/v1/organizations
604
+ Create or update an organization.
605
+
606
+ **Body:**
607
+ \`\`\`json
608
+ {
609
+ "name": "Acme Inc",
610
+ "domain": "acme.com",
611
+ "properties": {
612
+ "plan": "enterprise",
613
+ "seats": 50
614
+ }
615
+ }
616
+ \`\`\`
617
+
618
+ ### Releases (Changelog)
619
+
620
+ #### GET /api/v1/releases/public?workspace=YOUR_WORKSPACE_SLUG
621
+ Get published releases (public, no auth required).
622
+
623
+ #### POST /api/v1/releases
624
+ Create a new release.
625
+
626
+ **Body:**
627
+ \`\`\`json
628
+ {
629
+ "title": "v2.1.0",
630
+ "content": "## What's New\\n- Dark mode\\n- Bug fixes",
631
+ "status": "published",
632
+ "publishedAt": 1234567890
633
+ }
634
+ \`\`\`
635
+
636
+ ## Error Responses
637
+
638
+ All errors return:
639
+ \`\`\`json
640
+ {
641
+ "error": "Error message here"
642
+ }
643
+ \`\`\`
644
+
645
+ Common status codes:
646
+ - 400: Bad request (missing/invalid fields)
647
+ - 401: Unauthorized (missing/invalid API key)
648
+ - 404: Not found
649
+ - 429: Rate limited
650
+ - 500: Server error
651
+ `,
652
+ "docs://simple-product/people-api": `# People & Organizations API
653
+
654
+ Track your customers and their organizations in Simple Product.
655
+
656
+ ## Identifying Customers
657
+
658
+ Simple Product uses these fields to identify customers (in order of priority):
659
+
660
+ 1. **externalId** - Your internal user ID (most reliable)
661
+ 2. **email** - Customer's email address
662
+
663
+ When you submit feedback or call the People API with these identifiers, Simple Product will:
664
+ - Create a new customer record if none exists
665
+ - Update the existing record if found
666
+ - Link feedback to the customer
667
+
668
+ ## JavaScript SDK Pattern
669
+
670
+ \`\`\`javascript
671
+ class SimpleProduct {
672
+ constructor(apiKey) {
673
+ this.apiKey = apiKey;
674
+ this.baseUrl = '${baseUrl}/api/v1';
675
+ }
676
+
677
+ async identify(user) {
678
+ // Call this when a user logs in or updates their profile
679
+ return fetch(\`\${this.baseUrl}/people\`, {
680
+ method: 'POST',
681
+ headers: {
682
+ 'Authorization': \`Bearer \${this.apiKey}\`,
683
+ 'Content-Type': 'application/json',
684
+ },
685
+ body: JSON.stringify({
686
+ externalId: user.id,
687
+ email: user.email,
688
+ name: user.name,
689
+ properties: {
690
+ plan: user.plan,
691
+ createdAt: user.createdAt,
692
+ // Add any custom properties
693
+ },
694
+ }),
695
+ });
696
+ }
697
+
698
+ async feedback(content, options = {}) {
699
+ return fetch(\`\${this.baseUrl}/feedback\`, {
700
+ method: 'POST',
701
+ headers: {
702
+ 'Authorization': \`Bearer \${this.apiKey}\`,
703
+ 'Content-Type': 'application/json',
704
+ },
705
+ body: JSON.stringify({
706
+ title: options.title || 'Feedback',
707
+ content,
708
+ externalId: options.userId,
709
+ email: options.email,
710
+ source: options.source || 'sdk',
711
+ metadata: options.metadata,
712
+ }),
713
+ });
714
+ }
715
+ }
716
+
717
+ // Usage
718
+ const sp = new SimpleProduct('your_api_key');
719
+
720
+ // When user logs in
721
+ await sp.identify({
722
+ id: 'user_123',
723
+ email: 'john@example.com',
724
+ name: 'John Doe',
725
+ plan: 'pro',
726
+ });
727
+
728
+ // When user submits feedback
729
+ await sp.feedback('Love the new feature!', {
730
+ userId: 'user_123',
731
+ source: 'feedback-modal',
732
+ });
733
+ \`\`\`
734
+
735
+ ## Organizations
736
+
737
+ Link customers to organizations for B2B use cases:
738
+
739
+ \`\`\`javascript
740
+ // Create/update organization
741
+ await fetch('${baseUrl}/api/v1/organizations', {
742
+ method: 'POST',
743
+ headers: {
744
+ 'Authorization': 'Bearer YOUR_API_KEY',
745
+ 'Content-Type': 'application/json',
746
+ },
747
+ body: JSON.stringify({
748
+ name: 'Acme Inc',
749
+ domain: 'acme.com',
750
+ properties: {
751
+ plan: 'enterprise',
752
+ seats: 50,
753
+ industry: 'Technology',
754
+ },
755
+ }),
756
+ });
757
+ \`\`\`
758
+
759
+ Organizations are automatically linked to people based on email domain.
760
+
761
+ ## Best Practices
762
+
763
+ 1. **Always include externalId** - Email can change, your user ID won't
764
+ 2. **Call identify on login** - Keep customer data fresh
765
+ 3. **Use properties for segmentation** - Plan, role, company size, etc.
766
+ 4. **Track source on feedback** - Know where feedback comes from
767
+ `,
768
+ };
359
769
  // Define tools
360
770
  const tools = [
361
771
  {
@@ -551,14 +961,1017 @@ async function runServer() {
551
961
  required: ["query"],
552
962
  },
553
963
  },
964
+ // Linking tools - Doc-Card
965
+ {
966
+ name: "link_doc_to_card",
967
+ description: "Link a document to a card. Useful for associating specs, notes, or documentation with a task or feature.",
968
+ inputSchema: {
969
+ type: "object",
970
+ properties: {
971
+ docId: { type: "string", description: "The ID of the document to link" },
972
+ cardId: { type: "string", description: "The ID of the card to link the document to" },
973
+ },
974
+ required: ["docId", "cardId"],
975
+ },
976
+ },
977
+ {
978
+ name: "unlink_doc_from_card",
979
+ description: "Remove the link between a document and a card",
980
+ inputSchema: {
981
+ type: "object",
982
+ properties: {
983
+ docId: { type: "string", description: "The ID of the document to unlink" },
984
+ cardId: { type: "string", description: "The ID of the card to unlink from" },
985
+ },
986
+ required: ["docId", "cardId"],
987
+ },
988
+ },
989
+ {
990
+ name: "get_docs_for_card",
991
+ description: "Get all documents linked to a card",
992
+ inputSchema: {
993
+ type: "object",
994
+ properties: {
995
+ cardId: { type: "string", description: "The ID of the card" },
996
+ },
997
+ required: ["cardId"],
998
+ },
999
+ },
1000
+ {
1001
+ name: "get_cards_for_doc",
1002
+ description: "Get all cards linked to a document",
1003
+ inputSchema: {
1004
+ type: "object",
1005
+ properties: {
1006
+ docId: { type: "string", description: "The ID of the document" },
1007
+ },
1008
+ required: ["docId"],
1009
+ },
1010
+ },
1011
+ // Linking tools - Doc-Person
1012
+ {
1013
+ name: "link_doc_to_person",
1014
+ description: "Link a document to a person. Useful for associating meeting notes or feedback with a customer.",
1015
+ inputSchema: {
1016
+ type: "object",
1017
+ properties: {
1018
+ docId: { type: "string", description: "The ID of the document to link" },
1019
+ personId: { type: "string", description: "The ID of the person to link the document to" },
1020
+ },
1021
+ required: ["docId", "personId"],
1022
+ },
1023
+ },
1024
+ {
1025
+ name: "unlink_doc_from_person",
1026
+ description: "Remove the link between a document and a person",
1027
+ inputSchema: {
1028
+ type: "object",
1029
+ properties: {
1030
+ docId: { type: "string", description: "The ID of the document to unlink" },
1031
+ personId: { type: "string", description: "The ID of the person to unlink from" },
1032
+ },
1033
+ required: ["docId", "personId"],
1034
+ },
1035
+ },
1036
+ {
1037
+ name: "get_docs_for_person",
1038
+ description: "Get all documents linked to a person",
1039
+ inputSchema: {
1040
+ type: "object",
1041
+ properties: {
1042
+ personId: { type: "string", description: "The ID of the person" },
1043
+ },
1044
+ required: ["personId"],
1045
+ },
1046
+ },
1047
+ {
1048
+ name: "get_people_for_doc",
1049
+ description: "Get all people linked to a document",
1050
+ inputSchema: {
1051
+ type: "object",
1052
+ properties: {
1053
+ docId: { type: "string", description: "The ID of the document" },
1054
+ },
1055
+ required: ["docId"],
1056
+ },
1057
+ },
1058
+ // Linking tools - Card-Person
1059
+ {
1060
+ name: "link_card_to_person",
1061
+ description: "Link a card to a person. Useful for tracking which customers requested a feature.",
1062
+ inputSchema: {
1063
+ type: "object",
1064
+ properties: {
1065
+ cardId: { type: "string", description: "The ID of the card to link" },
1066
+ personId: { type: "string", description: "The ID of the person to link the card to" },
1067
+ },
1068
+ required: ["cardId", "personId"],
1069
+ },
1070
+ },
1071
+ {
1072
+ name: "unlink_card_from_person",
1073
+ description: "Remove the link between a card and a person",
1074
+ inputSchema: {
1075
+ type: "object",
1076
+ properties: {
1077
+ cardId: { type: "string", description: "The ID of the card to unlink" },
1078
+ personId: { type: "string", description: "The ID of the person to unlink from" },
1079
+ },
1080
+ required: ["cardId", "personId"],
1081
+ },
1082
+ },
1083
+ {
1084
+ name: "get_cards_for_person",
1085
+ description: "Get all cards linked to a person",
1086
+ inputSchema: {
1087
+ type: "object",
1088
+ properties: {
1089
+ personId: { type: "string", description: "The ID of the person" },
1090
+ },
1091
+ required: ["personId"],
1092
+ },
1093
+ },
1094
+ {
1095
+ name: "get_people_for_card",
1096
+ description: "Get all people linked to a card",
1097
+ inputSchema: {
1098
+ type: "object",
1099
+ properties: {
1100
+ cardId: { type: "string", description: "The ID of the card" },
1101
+ },
1102
+ required: ["cardId"],
1103
+ },
1104
+ },
1105
+ {
1106
+ name: "get_setup_guide",
1107
+ description: "Get implementation guide for integrating Simple Product features into your app. Returns code examples and instructions tailored to your framework.",
1108
+ inputSchema: {
1109
+ type: "object",
1110
+ properties: {
1111
+ feature: {
1112
+ type: "string",
1113
+ enum: ["feedback", "people", "releases", "all"],
1114
+ description: "Which feature to get setup instructions for",
1115
+ },
1116
+ framework: {
1117
+ type: "string",
1118
+ enum: ["react", "nextjs", "vue", "html", "node", "python"],
1119
+ description: "Target framework for code examples (default: react)",
1120
+ },
1121
+ },
1122
+ required: ["feature"],
1123
+ },
1124
+ },
1125
+ // Release tools
1126
+ {
1127
+ name: "create_release",
1128
+ description: "Create a new release/changelog entry. Use this to document new features, bug fixes, or updates.",
1129
+ inputSchema: {
1130
+ type: "object",
1131
+ properties: {
1132
+ title: { type: "string", description: "The title of the release (e.g., 'v1.2.0 - New Dashboard Features')" },
1133
+ content: { type: "string", description: "The release notes content in markdown format" },
1134
+ },
1135
+ required: ["title"],
1136
+ },
1137
+ },
1138
+ {
1139
+ name: "update_release",
1140
+ description: "Update an existing release's title or content",
1141
+ inputSchema: {
1142
+ type: "object",
1143
+ properties: {
1144
+ releaseId: { type: "string", description: "The ID of the release to update" },
1145
+ title: { type: "string", description: "New title for the release" },
1146
+ content: { type: "string", description: "New release notes content" },
1147
+ },
1148
+ required: ["releaseId"],
1149
+ },
1150
+ },
1151
+ {
1152
+ name: "get_release",
1153
+ description: "Get a release's full content by ID",
1154
+ inputSchema: {
1155
+ type: "object",
1156
+ properties: {
1157
+ releaseId: { type: "string", description: "The ID of the release to retrieve" },
1158
+ },
1159
+ required: ["releaseId"],
1160
+ },
1161
+ },
1162
+ {
1163
+ name: "search_releases",
1164
+ description: "Search for releases by title or content",
1165
+ inputSchema: {
1166
+ type: "object",
1167
+ properties: {
1168
+ query: { type: "string", description: "Search query" },
1169
+ },
1170
+ required: ["query"],
1171
+ },
1172
+ },
1173
+ {
1174
+ name: "list_releases",
1175
+ description: "List all releases in the workspace, sorted by date (newest first)",
1176
+ inputSchema: {
1177
+ type: "object",
1178
+ properties: {},
1179
+ },
1180
+ },
1181
+ // Feedback tools
1182
+ {
1183
+ name: "create_feedback",
1184
+ description: "Create a new feedback entry. Use this to capture user feedback, feature requests, or bug reports.",
1185
+ inputSchema: {
1186
+ type: "object",
1187
+ properties: {
1188
+ title: { type: "string", description: "The title/summary of the feedback" },
1189
+ content: { type: "string", description: "The detailed feedback content in markdown format" },
1190
+ personId: { type: "string", description: "Optional ID of the person who provided this feedback" },
1191
+ },
1192
+ required: ["title"],
1193
+ },
1194
+ },
1195
+ {
1196
+ name: "update_feedback",
1197
+ description: "Update an existing feedback entry's title or content",
1198
+ inputSchema: {
1199
+ type: "object",
1200
+ properties: {
1201
+ feedbackId: { type: "string", description: "The ID of the feedback to update" },
1202
+ title: { type: "string", description: "New title for the feedback" },
1203
+ content: { type: "string", description: "New content for the feedback" },
1204
+ },
1205
+ required: ["feedbackId"],
1206
+ },
1207
+ },
1208
+ {
1209
+ name: "get_feedback",
1210
+ description: "Get a feedback entry's full content by ID, including the linked person if any",
1211
+ inputSchema: {
1212
+ type: "object",
1213
+ properties: {
1214
+ feedbackId: { type: "string", description: "The ID of the feedback to retrieve" },
1215
+ },
1216
+ required: ["feedbackId"],
1217
+ },
1218
+ },
1219
+ {
1220
+ name: "search_feedback",
1221
+ description: "Search for feedback by title or content",
1222
+ inputSchema: {
1223
+ type: "object",
1224
+ properties: {
1225
+ query: { type: "string", description: "Search query" },
1226
+ },
1227
+ required: ["query"],
1228
+ },
1229
+ },
1230
+ {
1231
+ name: "list_feedback",
1232
+ description: "List all feedback in the workspace, sorted by date (newest first)",
1233
+ inputSchema: {
1234
+ type: "object",
1235
+ properties: {},
1236
+ },
1237
+ },
554
1238
  ];
1239
+ // Handle list resources
1240
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1241
+ return { resources };
1242
+ });
1243
+ // Handle read resource
1244
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1245
+ const { uri } = request.params;
1246
+ const content = resourceContents[uri];
1247
+ if (!content) {
1248
+ throw new Error(`Resource not found: ${uri}`);
1249
+ }
1250
+ return {
1251
+ contents: [
1252
+ {
1253
+ uri,
1254
+ mimeType: "text/markdown",
1255
+ text: content,
1256
+ },
1257
+ ],
1258
+ };
1259
+ });
555
1260
  // Handle list tools
556
1261
  server.setRequestHandler(ListToolsRequestSchema, async () => {
557
1262
  return { tools };
558
1263
  });
1264
+ // Setup guide content generator
1265
+ const generateSetupGuide = (feature, framework = "react") => {
1266
+ const guides = {
1267
+ feedback: {
1268
+ react: `# Feedback Collection - React Setup
1269
+
1270
+ ## Quick Implementation
1271
+
1272
+ Add this component to your app to collect customer feedback:
1273
+
1274
+ \`\`\`tsx
1275
+ import { useState } from 'react';
1276
+
1277
+ interface FeedbackWidgetProps {
1278
+ apiKey: string;
1279
+ userEmail?: string;
1280
+ userName?: string;
1281
+ }
1282
+
1283
+ export function FeedbackWidget({ apiKey, userEmail, userName }: FeedbackWidgetProps) {
1284
+ const [isOpen, setIsOpen] = useState(false);
1285
+ const [content, setContent] = useState('');
1286
+ const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
1287
+
1288
+ const handleSubmit = async (e: React.FormEvent) => {
1289
+ e.preventDefault();
1290
+ setStatus('sending');
1291
+
1292
+ try {
1293
+ const res = await fetch('${baseUrl}/api/v1/feedback', {
1294
+ method: 'POST',
1295
+ headers: {
1296
+ 'Authorization': \`Bearer \${apiKey}\`,
1297
+ 'Content-Type': 'application/json',
1298
+ },
1299
+ body: JSON.stringify({
1300
+ title: 'Feedback',
1301
+ content,
1302
+ email: userEmail,
1303
+ name: userName,
1304
+ source: 'react-widget',
1305
+ metadata: { page: window.location.pathname },
1306
+ }),
1307
+ });
1308
+
1309
+ if (res.ok) {
1310
+ setStatus('sent');
1311
+ setTimeout(() => {
1312
+ setIsOpen(false);
1313
+ setStatus('idle');
1314
+ setContent('');
1315
+ }, 2000);
1316
+ } else {
1317
+ setStatus('error');
1318
+ }
1319
+ } catch {
1320
+ setStatus('error');
1321
+ }
1322
+ };
1323
+
1324
+ if (!isOpen) {
1325
+ return (
1326
+ <button
1327
+ onClick={() => setIsOpen(true)}
1328
+ style={{
1329
+ position: 'fixed',
1330
+ bottom: '20px',
1331
+ right: '20px',
1332
+ padding: '12px 24px',
1333
+ borderRadius: '8px',
1334
+ border: 'none',
1335
+ background: '#000',
1336
+ color: '#fff',
1337
+ cursor: 'pointer',
1338
+ }}
1339
+ >
1340
+ Feedback
1341
+ </button>
1342
+ );
1343
+ }
1344
+
1345
+ return (
1346
+ <div style={{
1347
+ position: 'fixed',
1348
+ bottom: '20px',
1349
+ right: '20px',
1350
+ width: '320px',
1351
+ padding: '20px',
1352
+ borderRadius: '12px',
1353
+ background: '#fff',
1354
+ boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
1355
+ }}>
1356
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px' }}>
1357
+ <h3 style={{ margin: 0 }}>Send Feedback</h3>
1358
+ <button onClick={() => setIsOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>✕</button>
1359
+ </div>
1360
+
1361
+ {status === 'sent' ? (
1362
+ <p>Thanks for your feedback!</p>
1363
+ ) : (
1364
+ <form onSubmit={handleSubmit}>
1365
+ <textarea
1366
+ value={content}
1367
+ onChange={(e) => setContent(e.target.value)}
1368
+ placeholder="What's on your mind?"
1369
+ required
1370
+ style={{ width: '100%', minHeight: '100px', marginBottom: '12px', padding: '8px', borderRadius: '6px', border: '1px solid #ddd' }}
1371
+ />
1372
+ <button
1373
+ type="submit"
1374
+ disabled={status === 'sending'}
1375
+ style={{ width: '100%', padding: '10px', borderRadius: '6px', border: 'none', background: '#000', color: '#fff', cursor: 'pointer' }}
1376
+ >
1377
+ {status === 'sending' ? 'Sending...' : 'Send'}
1378
+ </button>
1379
+ {status === 'error' && <p style={{ color: 'red', marginTop: '8px' }}>Something went wrong. Please try again.</p>}
1380
+ </form>
1381
+ )}
1382
+ </div>
1383
+ );
1384
+ }
1385
+ \`\`\`
1386
+
1387
+ ## Usage
1388
+
1389
+ \`\`\`tsx
1390
+ // In your app layout or main component
1391
+ import { FeedbackWidget } from './FeedbackWidget';
1392
+
1393
+ function App() {
1394
+ return (
1395
+ <>
1396
+ {/* Your app content */}
1397
+ <FeedbackWidget
1398
+ apiKey="YOUR_API_KEY"
1399
+ userEmail={currentUser?.email}
1400
+ userName={currentUser?.name}
1401
+ />
1402
+ </>
1403
+ );
1404
+ }
1405
+ \`\`\`
1406
+
1407
+ ## Get Your API Key
1408
+
1409
+ 1. Go to Settings → API Keys in Simple Product
1410
+ 2. Create a new API key
1411
+ 3. Replace YOUR_API_KEY with your actual key
1412
+ `,
1413
+ nextjs: `# Feedback Collection - Next.js Setup
1414
+
1415
+ ## Server Action Approach (Recommended)
1416
+
1417
+ ### 1. Create a Server Action
1418
+
1419
+ \`\`\`typescript
1420
+ // app/actions/feedback.ts
1421
+ 'use server';
1422
+
1423
+ export async function submitFeedback(formData: FormData) {
1424
+ const content = formData.get('content') as string;
1425
+ const email = formData.get('email') as string;
1426
+
1427
+ const response = await fetch('${baseUrl}/api/v1/feedback', {
1428
+ method: 'POST',
1429
+ headers: {
1430
+ 'Authorization': \`Bearer \${process.env.SIMPLE_PRODUCT_API_KEY}\`,
1431
+ 'Content-Type': 'application/json',
1432
+ },
1433
+ body: JSON.stringify({
1434
+ title: 'Feedback',
1435
+ content,
1436
+ email: email || undefined,
1437
+ source: 'nextjs-app',
1438
+ }),
1439
+ });
1440
+
1441
+ if (!response.ok) {
1442
+ throw new Error('Failed to submit feedback');
1443
+ }
1444
+
1445
+ return { success: true };
1446
+ }
1447
+ \`\`\`
1448
+
1449
+ ### 2. Create a Feedback Form Component
1450
+
1451
+ \`\`\`tsx
1452
+ // components/feedback-form.tsx
1453
+ 'use client';
1454
+
1455
+ import { useActionState } from 'react';
1456
+ import { submitFeedback } from '@/app/actions/feedback';
1457
+
1458
+ export function FeedbackForm() {
1459
+ const [state, formAction, isPending] = useActionState(submitFeedback, null);
1460
+
1461
+ return (
1462
+ <form action={formAction}>
1463
+ <textarea name="content" placeholder="Your feedback..." required />
1464
+ <input type="email" name="email" placeholder="Email (optional)" />
1465
+ <button type="submit" disabled={isPending}>
1466
+ {isPending ? 'Sending...' : 'Send Feedback'}
1467
+ </button>
1468
+ {state?.success && <p>Thanks for your feedback!</p>}
1469
+ </form>
1470
+ );
1471
+ }
1472
+ \`\`\`
1473
+
1474
+ ### 3. Add Environment Variable
1475
+
1476
+ \`\`\`bash
1477
+ # .env.local
1478
+ SIMPLE_PRODUCT_API_KEY=your_api_key_here
1479
+ \`\`\`
1480
+ `,
1481
+ html: `# Feedback Collection - HTML Form
1482
+
1483
+ The simplest way to collect feedback - no JavaScript required:
1484
+
1485
+ \`\`\`html
1486
+ <form
1487
+ action="${baseUrl}/api/v1/feedback?key=YOUR_API_KEY&redirect=https://yoursite.com/thanks"
1488
+ method="POST"
1489
+ style="max-width: 400px; margin: 0 auto;"
1490
+ >
1491
+ <input type="hidden" name="title" value="Feedback" />
1492
+
1493
+ <div style="margin-bottom: 12px;">
1494
+ <input
1495
+ type="email"
1496
+ name="email"
1497
+ placeholder="Email (optional)"
1498
+ style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px;"
1499
+ />
1500
+ </div>
1501
+
1502
+ <div style="margin-bottom: 12px;">
1503
+ <input
1504
+ type="text"
1505
+ name="name"
1506
+ placeholder="Name (optional)"
1507
+ style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px;"
1508
+ />
1509
+ </div>
1510
+
1511
+ <div style="margin-bottom: 12px;">
1512
+ <textarea
1513
+ name="content"
1514
+ placeholder="Your feedback..."
1515
+ required
1516
+ style="width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 6px;"
1517
+ ></textarea>
1518
+ </div>
1519
+
1520
+ <button
1521
+ type="submit"
1522
+ style="width: 100%; padding: 12px; background: #000; color: #fff; border: none; border-radius: 6px; cursor: pointer;"
1523
+ >
1524
+ Send Feedback
1525
+ </button>
1526
+ </form>
1527
+ \`\`\`
1528
+
1529
+ After submission, users are redirected to your URL with \`?success=true\` or \`?error=message\`.
1530
+ `,
1531
+ node: `# Feedback Collection - Node.js/Express
1532
+
1533
+ ## API Integration
1534
+
1535
+ \`\`\`javascript
1536
+ // feedback.js
1537
+ const SIMPLE_PRODUCT_API_KEY = process.env.SIMPLE_PRODUCT_API_KEY;
1538
+ const SIMPLE_PRODUCT_URL = '${baseUrl}';
1539
+
1540
+ async function submitFeedback({ title, content, email, name, source, metadata }) {
1541
+ const response = await fetch(\`\${SIMPLE_PRODUCT_URL}/api/v1/feedback\`, {
1542
+ method: 'POST',
1543
+ headers: {
1544
+ 'Authorization': \`Bearer \${SIMPLE_PRODUCT_API_KEY}\`,
1545
+ 'Content-Type': 'application/json',
1546
+ },
1547
+ body: JSON.stringify({
1548
+ title: title || 'Feedback',
1549
+ content,
1550
+ email,
1551
+ name,
1552
+ source: source || 'api',
1553
+ metadata,
1554
+ }),
1555
+ });
1556
+
1557
+ if (!response.ok) {
1558
+ const error = await response.json();
1559
+ throw new Error(error.error || 'Failed to submit feedback');
1560
+ }
1561
+
1562
+ return response.json();
1563
+ }
1564
+
1565
+ module.exports = { submitFeedback };
1566
+ \`\`\`
1567
+
1568
+ ## Express Route Example
1569
+
1570
+ \`\`\`javascript
1571
+ const express = require('express');
1572
+ const { submitFeedback } = require('./feedback');
1573
+
1574
+ const app = express();
1575
+ app.use(express.json());
1576
+
1577
+ app.post('/api/feedback', async (req, res) => {
1578
+ try {
1579
+ const { content, email, name } = req.body;
1580
+ const result = await submitFeedback({
1581
+ content,
1582
+ email,
1583
+ name,
1584
+ source: 'express-api',
1585
+ });
1586
+ res.json(result);
1587
+ } catch (error) {
1588
+ res.status(500).json({ error: error.message });
1589
+ }
1590
+ });
1591
+ \`\`\`
1592
+ `,
1593
+ python: `# Feedback Collection - Python
1594
+
1595
+ ## Using requests library
1596
+
1597
+ \`\`\`python
1598
+ import os
1599
+ import requests
1600
+
1601
+ SIMPLE_PRODUCT_API_KEY = os.environ.get('SIMPLE_PRODUCT_API_KEY')
1602
+ SIMPLE_PRODUCT_URL = '${baseUrl}'
1603
+
1604
+ def submit_feedback(content, title='Feedback', email=None, name=None, source='python-sdk', metadata=None):
1605
+ """Submit feedback to Simple Product."""
1606
+ response = requests.post(
1607
+ f'{SIMPLE_PRODUCT_URL}/api/v1/feedback',
1608
+ headers={
1609
+ 'Authorization': f'Bearer {SIMPLE_PRODUCT_API_KEY}',
1610
+ 'Content-Type': 'application/json',
1611
+ },
1612
+ json={
1613
+ 'title': title,
1614
+ 'content': content,
1615
+ 'email': email,
1616
+ 'name': name,
1617
+ 'source': source,
1618
+ 'metadata': metadata or {},
1619
+ }
1620
+ )
1621
+ response.raise_for_status()
1622
+ return response.json()
1623
+
1624
+ # Usage
1625
+ result = submit_feedback(
1626
+ content='Great product!',
1627
+ email='user@example.com',
1628
+ name='John Doe'
1629
+ )
1630
+ print(result)
1631
+ \`\`\`
1632
+
1633
+ ## Flask Example
1634
+
1635
+ \`\`\`python
1636
+ from flask import Flask, request, jsonify
1637
+
1638
+ app = Flask(__name__)
1639
+
1640
+ @app.route('/feedback', methods=['POST'])
1641
+ def feedback():
1642
+ data = request.json
1643
+ result = submit_feedback(
1644
+ content=data['content'],
1645
+ email=data.get('email'),
1646
+ name=data.get('name'),
1647
+ source='flask-api'
1648
+ )
1649
+ return jsonify(result)
1650
+ \`\`\`
1651
+ `,
1652
+ vue: `# Feedback Collection - Vue.js Setup
1653
+
1654
+ ## Composable
1655
+
1656
+ \`\`\`typescript
1657
+ // composables/useFeedback.ts
1658
+ import { ref } from 'vue';
1659
+
1660
+ export function useFeedback(apiKey: string) {
1661
+ const isSubmitting = ref(false);
1662
+ const isSuccess = ref(false);
1663
+ const error = ref<string | null>(null);
1664
+
1665
+ async function submitFeedback(content: string, options?: {
1666
+ email?: string;
1667
+ name?: string;
1668
+ source?: string;
1669
+ }) {
1670
+ isSubmitting.value = true;
1671
+ error.value = null;
1672
+
1673
+ try {
1674
+ const res = await fetch('${baseUrl}/api/v1/feedback', {
1675
+ method: 'POST',
1676
+ headers: {
1677
+ 'Authorization': \`Bearer \${apiKey}\`,
1678
+ 'Content-Type': 'application/json',
1679
+ },
1680
+ body: JSON.stringify({
1681
+ title: 'Feedback',
1682
+ content,
1683
+ ...options,
1684
+ source: options?.source || 'vue-widget',
1685
+ }),
1686
+ });
1687
+
1688
+ if (!res.ok) throw new Error('Failed to submit');
1689
+
1690
+ isSuccess.value = true;
1691
+ return await res.json();
1692
+ } catch (e) {
1693
+ error.value = e instanceof Error ? e.message : 'Unknown error';
1694
+ throw e;
1695
+ } finally {
1696
+ isSubmitting.value = false;
1697
+ }
1698
+ }
1699
+
1700
+ return { submitFeedback, isSubmitting, isSuccess, error };
1701
+ }
1702
+ \`\`\`
1703
+
1704
+ ## Component
1705
+
1706
+ \`\`\`vue
1707
+ <script setup lang="ts">
1708
+ import { ref } from 'vue';
1709
+ import { useFeedback } from '@/composables/useFeedback';
1710
+
1711
+ const { submitFeedback, isSubmitting, isSuccess, error } = useFeedback('YOUR_API_KEY');
1712
+ const content = ref('');
1713
+
1714
+ async function handleSubmit() {
1715
+ await submitFeedback(content.value);
1716
+ content.value = '';
1717
+ }
1718
+ </script>
1719
+
1720
+ <template>
1721
+ <form @submit.prevent="handleSubmit">
1722
+ <textarea v-model="content" placeholder="Your feedback..." required />
1723
+ <button type="submit" :disabled="isSubmitting">
1724
+ {{ isSubmitting ? 'Sending...' : 'Send Feedback' }}
1725
+ </button>
1726
+ <p v-if="isSuccess">Thanks for your feedback!</p>
1727
+ <p v-if="error" class="error">{{ error }}</p>
1728
+ </form>
1729
+ </template>
1730
+ \`\`\`
1731
+ `,
1732
+ },
1733
+ people: {
1734
+ react: resourceContents["docs://simple-product/people-api"],
1735
+ nextjs: resourceContents["docs://simple-product/people-api"],
1736
+ vue: resourceContents["docs://simple-product/people-api"],
1737
+ html: resourceContents["docs://simple-product/people-api"],
1738
+ node: resourceContents["docs://simple-product/people-api"],
1739
+ python: resourceContents["docs://simple-product/people-api"],
1740
+ },
1741
+ releases: {
1742
+ react: `# Releases API - Display Changelog in Your App
1743
+
1744
+ ## Fetch Published Releases
1745
+
1746
+ \`\`\`typescript
1747
+ // hooks/useReleases.ts
1748
+ import { useState, useEffect } from 'react';
1749
+
1750
+ interface Release {
1751
+ id: string;
1752
+ title: string;
1753
+ content: string;
1754
+ publishedAt: number;
1755
+ }
1756
+
1757
+ export function useReleases(workspaceSlug: string) {
1758
+ const [releases, setReleases] = useState<Release[]>([]);
1759
+ const [isLoading, setIsLoading] = useState(true);
1760
+
1761
+ useEffect(() => {
1762
+ fetch(\`${baseUrl}/api/v1/releases/public?workspace=\${workspaceSlug}\`)
1763
+ .then(res => res.json())
1764
+ .then(data => {
1765
+ setReleases(data.data || []);
1766
+ setIsLoading(false);
1767
+ });
1768
+ }, [workspaceSlug]);
1769
+
1770
+ return { releases, isLoading };
1771
+ }
1772
+ \`\`\`
1773
+
1774
+ ## Changelog Component
1775
+
1776
+ \`\`\`tsx
1777
+ import { useReleases } from '@/hooks/useReleases';
1778
+
1779
+ export function Changelog({ workspaceSlug }: { workspaceSlug: string }) {
1780
+ const { releases, isLoading } = useReleases(workspaceSlug);
1781
+
1782
+ if (isLoading) return <p>Loading...</p>;
1783
+
1784
+ return (
1785
+ <div>
1786
+ <h2>What's New</h2>
1787
+ {releases.map(release => (
1788
+ <article key={release.id}>
1789
+ <h3>{release.title}</h3>
1790
+ <time>{new Date(release.publishedAt).toLocaleDateString()}</time>
1791
+ <div dangerouslySetInnerHTML={{ __html: release.content }} />
1792
+ </article>
1793
+ ))}
1794
+ </div>
1795
+ );
1796
+ }
1797
+ \`\`\`
1798
+
1799
+ ## RSS Feed
1800
+
1801
+ Subscribe to releases via RSS:
1802
+ \`\`\`
1803
+ ${baseUrl}/api/v1/releases/rss?workspace=YOUR_WORKSPACE_SLUG
1804
+ \`\`\`
1805
+ `,
1806
+ nextjs: `# Releases API - Next.js Integration
1807
+
1808
+ ## Server Component
1809
+
1810
+ \`\`\`tsx
1811
+ // app/changelog/page.tsx
1812
+ async function getReleases(workspaceSlug: string) {
1813
+ const res = await fetch(
1814
+ \`${baseUrl}/api/v1/releases/public?workspace=\${workspaceSlug}\`,
1815
+ { next: { revalidate: 3600 } } // Cache for 1 hour
1816
+ );
1817
+ return res.json();
1818
+ }
1819
+
1820
+ export default async function ChangelogPage() {
1821
+ const { data: releases } = await getReleases('your-workspace-slug');
1822
+
1823
+ return (
1824
+ <main>
1825
+ <h1>Changelog</h1>
1826
+ {releases.map((release: any) => (
1827
+ <article key={release.id}>
1828
+ <h2>{release.title}</h2>
1829
+ <time>{new Date(release.publishedAt).toLocaleDateString()}</time>
1830
+ <div dangerouslySetInnerHTML={{ __html: release.content }} />
1831
+ </article>
1832
+ ))}
1833
+ </main>
1834
+ );
1835
+ }
1836
+ \`\`\`
1837
+ `,
1838
+ html: `# Releases - Embed Changelog
1839
+
1840
+ \`\`\`html
1841
+ <div id="changelog"></div>
1842
+
1843
+ <script>
1844
+ fetch('${baseUrl}/api/v1/releases/public?workspace=YOUR_WORKSPACE_SLUG')
1845
+ .then(res => res.json())
1846
+ .then(({ data }) => {
1847
+ const container = document.getElementById('changelog');
1848
+ container.innerHTML = data.map(release => \`
1849
+ <article>
1850
+ <h3>\${release.title}</h3>
1851
+ <time>\${new Date(release.publishedAt).toLocaleDateString()}</time>
1852
+ <div>\${release.content}</div>
1853
+ </article>
1854
+ \`).join('');
1855
+ });
1856
+ </script>
1857
+ \`\`\`
1858
+ `,
1859
+ vue: `# Releases - Vue.js Integration
1860
+
1861
+ \`\`\`vue
1862
+ <script setup lang="ts">
1863
+ import { ref, onMounted } from 'vue';
1864
+
1865
+ const releases = ref([]);
1866
+ const workspaceSlug = 'your-workspace-slug';
1867
+
1868
+ onMounted(async () => {
1869
+ const res = await fetch(\`${baseUrl}/api/v1/releases/public?workspace=\${workspaceSlug}\`);
1870
+ const { data } = await res.json();
1871
+ releases.value = data;
1872
+ });
1873
+ </script>
1874
+
1875
+ <template>
1876
+ <div>
1877
+ <h2>What's New</h2>
1878
+ <article v-for="release in releases" :key="release.id">
1879
+ <h3>{{ release.title }}</h3>
1880
+ <time>{{ new Date(release.publishedAt).toLocaleDateString() }}</time>
1881
+ <div v-html="release.content" />
1882
+ </article>
1883
+ </div>
1884
+ </template>
1885
+ \`\`\`
1886
+ `,
1887
+ node: `# Releases API - Node.js
1888
+
1889
+ \`\`\`javascript
1890
+ async function getPublishedReleases(workspaceSlug) {
1891
+ const response = await fetch(
1892
+ \`${baseUrl}/api/v1/releases/public?workspace=\${workspaceSlug}\`
1893
+ );
1894
+ const { data } = await response.json();
1895
+ return data;
1896
+ }
1897
+
1898
+ // Create a release (authenticated)
1899
+ async function createRelease({ title, content, status = 'draft' }) {
1900
+ const response = await fetch('${baseUrl}/api/v1/releases', {
1901
+ method: 'POST',
1902
+ headers: {
1903
+ 'Authorization': \`Bearer \${process.env.SIMPLE_PRODUCT_API_KEY}\`,
1904
+ 'Content-Type': 'application/json',
1905
+ },
1906
+ body: JSON.stringify({
1907
+ title,
1908
+ content,
1909
+ status,
1910
+ publishedAt: status === 'published' ? Date.now() : undefined,
1911
+ }),
1912
+ });
1913
+ return response.json();
1914
+ }
1915
+ \`\`\`
1916
+ `,
1917
+ python: `# Releases API - Python
1918
+
1919
+ \`\`\`python
1920
+ import requests
1921
+
1922
+ def get_published_releases(workspace_slug):
1923
+ """Get all published releases (public, no auth required)."""
1924
+ response = requests.get(
1925
+ f'${baseUrl}/api/v1/releases/public',
1926
+ params={'workspace': workspace_slug}
1927
+ )
1928
+ return response.json()['data']
1929
+
1930
+ def create_release(title, content, status='draft'):
1931
+ """Create a new release (requires API key)."""
1932
+ response = requests.post(
1933
+ '${baseUrl}/api/v1/releases',
1934
+ headers={
1935
+ 'Authorization': f'Bearer {os.environ["SIMPLE_PRODUCT_API_KEY"]}',
1936
+ 'Content-Type': 'application/json',
1937
+ },
1938
+ json={
1939
+ 'title': title,
1940
+ 'content': content,
1941
+ 'status': status,
1942
+ 'publishedAt': int(time.time() * 1000) if status == 'published' else None,
1943
+ }
1944
+ )
1945
+ return response.json()
1946
+ \`\`\`
1947
+ `,
1948
+ },
1949
+ all: {
1950
+ react: `${resourceContents["docs://simple-product/api-reference"]}`,
1951
+ nextjs: `${resourceContents["docs://simple-product/api-reference"]}`,
1952
+ vue: `${resourceContents["docs://simple-product/api-reference"]}`,
1953
+ html: `${resourceContents["docs://simple-product/api-reference"]}`,
1954
+ node: `${resourceContents["docs://simple-product/api-reference"]}`,
1955
+ python: `${resourceContents["docs://simple-product/api-reference"]}`,
1956
+ },
1957
+ };
1958
+ const featureGuide = guides[feature];
1959
+ if (!featureGuide) {
1960
+ return `Unknown feature: ${feature}. Available features: feedback, people, releases, all`;
1961
+ }
1962
+ return featureGuide[framework] || featureGuide.react || `No guide available for ${framework}`;
1963
+ };
559
1964
  // Handle tool calls
560
1965
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
561
1966
  const { name, arguments: args } = request.params;
1967
+ // Handle get_setup_guide locally
1968
+ if (name === "get_setup_guide") {
1969
+ const { feature, framework } = args;
1970
+ const guide = generateSetupGuide(feature, framework || "react");
1971
+ return {
1972
+ content: [{ type: "text", text: guide }],
1973
+ };
1974
+ }
562
1975
  try {
563
1976
  // Call the API endpoint
564
1977
  const response = await fetch(`${baseUrl}/api/mcp/execute`, {