This is page 1 of 5. Use http://codebase.md/marianfoo/mcp-sap-docs?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .cursor
│ └── rules
│ ├── 00-overview.mdc
│ ├── 10-search-stack.mdc
│ ├── 20-tools-and-apis.mdc
│ ├── 30-tests-and-output.mdc
│ ├── 40-deploy.mdc
│ ├── 50-metadata-config.mdc
│ ├── 60-adding-github-sources.mdc
│ ├── 70-tool-usage-guide.mdc
│ └── 80-abap-integration.mdc
├── .cursorignore
├── .gitattributes
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── config.yml
│ │ ├── missing-documentation.yml
│ │ └── new-documentation-source.yml
│ └── workflows
│ ├── deploy-mcp-sap-docs.yml
│ ├── test-pr.yml
│ └── update-submodules.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── docs
│ ├── ABAP-INTEGRATION-SUMMARY.md
│ ├── ABAP-MULTI-VERSION-INTEGRATION.md
│ ├── ABAP-STANDARD-INTEGRATION.md
│ ├── ABAP-USAGE-GUIDE.md
│ ├── ARCHITECTURE.md
│ ├── COMMUNITY-SEARCH-IMPLEMENTATION.md
│ ├── CONTENT-SIZE-LIMITS.md
│ ├── CURSOR-SETUP.md
│ ├── DEV.md
│ ├── FTS5-IMPLEMENTATION-COMPLETE.md
│ ├── LLM-FRIENDLY-IMPROVEMENTS.md
│ ├── METADATA-CONSOLIDATION.md
│ ├── TEST-SEARCH.md
│ └── TESTS.md
├── ecosystem.config.cjs
├── index.html
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── REMOTE_SETUP.md
├── scripts
│ ├── build-fts.ts
│ ├── build-index.ts
│ ├── check-version.js
│ └── summarize-src.js
├── server.json
├── setup.sh
├── src
│ ├── global.d.ts
│ ├── http-server.ts
│ ├── lib
│ │ ├── BaseServerHandler.ts
│ │ ├── communityBestMatch.ts
│ │ ├── config.ts
│ │ ├── localDocs.ts
│ │ ├── logger.ts
│ │ ├── metadata.ts
│ │ ├── sapHelp.ts
│ │ ├── search.ts
│ │ ├── searchDb.ts
│ │ ├── truncate.ts
│ │ ├── types.ts
│ │ └── url-generation
│ │ ├── abap.ts
│ │ ├── BaseUrlGenerator.ts
│ │ ├── cap.ts
│ │ ├── cloud-sdk.ts
│ │ ├── dsag.ts
│ │ ├── GenericUrlGenerator.ts
│ │ ├── index.ts
│ │ ├── README.md
│ │ ├── sapui5.ts
│ │ ├── utils.ts
│ │ └── wdi5.ts
│ ├── metadata.json
│ ├── server.ts
│ └── streamable-http-server.ts
├── test
│ ├── _utils
│ │ ├── httpClient.js
│ │ └── parseResults.js
│ ├── community-search.ts
│ ├── comprehensive-url-generation.test.ts
│ ├── performance
│ │ └── README.md
│ ├── prompts.test.ts
│ ├── quick-url-test.ts
│ ├── README.md
│ ├── tools
│ │ ├── run-tests.js
│ │ ├── sap_docs_search
│ │ │ ├── search-cap-docs.js
│ │ │ ├── search-cloud-sdk-ai.js
│ │ │ ├── search-cloud-sdk-js.js
│ │ │ └── search-sapui5-docs.js
│ │ ├── search-url-verification.js
│ │ ├── search.generic.spec.js
│ │ └── search.smoke.js
│ ├── url-status.ts
│ └── validate-urls.ts
├── test-community-search.js
├── test-search-interactive.ts
├── test-search.http
├── test-search.ts
├── tsconfig.json
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | .DS_Store
2 | node_modules
3 | dist
4 | data
5 | sources
6 | .cache
7 | src_context.txt
8 | test_context.txt
9 | .mcpregistry_github_token
10 | .mcpregistry_registry_token
11 |
```
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
1 | # Handle large files
2 | *.sqlite binary
3 | *.sqlite-shm binary
4 | *.sqlite-wal binary
5 | *.json -text
6 | *.md text
7 | *.ts text
8 | *.js text
9 |
10 | # Submodules
11 | sources/ -text
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
1 | # Development files
2 | src/
3 | test/
4 | docs/
5 | scripts/
6 | .github/
7 |
8 | # Large data files that shouldn't be in NPM package
9 | sources/
10 | data/
11 | dist/data/
12 | *.sqlite
13 | *.db
14 | *.sqlite-shm
15 | *.sqlite-wal
16 |
17 | # Development configuration
18 | tsconfig.json
19 | vitest.config.ts
20 | .DS_Store
21 | .cache
22 | ecosystem.config.cjs
23 | setup.sh
24 |
25 | # Test and development files
26 | test-*.ts
27 | test-*.js
28 | test-*.http
29 | *_context.txt
30 | server.json
31 |
32 | # CI/CD and deployment
33 | .github/
34 | REMOTE_SETUP.md
35 | index.html
36 |
37 | # Keep these important files
38 | !README.md
39 | !LICENSE
40 | !package.json
41 | !dist/
42 |
```
--------------------------------------------------------------------------------
/.cursorignore:
--------------------------------------------------------------------------------
```
1 | # Build output & caches
2 | dist/**
3 | node_modules/**
4 | .cache/**
5 | coverage/**
6 | *.log
7 | .npm/**
8 | .pnpm-store/**
9 |
10 | # Huge vendor docs & tests you don't want in context
11 | sources/**/test/**
12 | sources/openui5/**/test/**
13 | sources/**/.git/**
14 | sources/**/.github/**
15 | sources/**/node_modules/**
16 | sources/**/.cache/**
17 |
18 | # Large generated search artifacts
19 | dist/data/index.json
20 | dist/data/*.sqlite
21 | dist/data/*.db
22 |
23 | # Test artifacts and temporary files
24 | test-*.js
25 | debug-*.js
26 | *.tmp
27 | *.temp
28 |
29 | # IDE and editor files
30 | .vscode/**
31 | .idea/**
32 | *.swp
33 | *.swo
34 |
35 | # OS generated files
36 | .DS_Store
37 | Thumbs.db
38 |
39 | # Large documentation that's already indexed
40 | sources/sapui5-docs/docs/**/*.md
41 | sources/cap-docs/**/*.md
42 | sources/openui5/docs/**/*.md
43 | sources/wdi5/docs/**/*.md
44 |
45 | # Submodule git directories
46 | sources/**/.git
47 |
48 | # Large binary or generated files
49 | *.pdf
50 | *.zip
51 | *.tar.gz
52 | *.tgz
53 |
```
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
```
1 | [submodule "sources/sapui5-docs"]
2 | path = sources/sapui5-docs
3 | url = https://github.com/SAP-docs/sapui5.git
4 | branch = main
5 | [submodule "sources/cap-docs"]
6 | path = sources/cap-docs
7 | url = https://github.com/cap-js/docs.git
8 | branch = main
9 | [submodule "sources/openui5"]
10 | path = sources/openui5
11 | url = https://github.com/SAP/openui5.git
12 | branch = master
13 | [submodule "sources/wdi5"]
14 | path = sources/wdi5
15 | url = https://github.com/ui5-community/wdi5.git
16 | branch = main
17 | [submodule "sources/ui5-tooling"]
18 | path = sources/ui5-tooling
19 | url = https://github.com/SAP/ui5-tooling.git
20 | branch = main
21 | [submodule "sources/cloud-mta-build-tool"]
22 | path = sources/cloud-mta-build-tool
23 | url = https://github.com/SAP/cloud-mta-build-tool.git
24 | branch = master
25 | [submodule "sources/ui5-webcomponents"]
26 | path = sources/ui5-webcomponents
27 | url = https://github.com/SAP/ui5-webcomponents.git
28 | branch = main
29 | [submodule "sources/cloud-sdk"]
30 | path = sources/cloud-sdk
31 | url = https://github.com/SAP/cloud-sdk.git
32 | branch = main
33 | [submodule "sources/cloud-sdk-ai"]
34 | path = sources/cloud-sdk-ai
35 | url = https://github.com/SAP/ai-sdk.git
36 | branch = main
37 | [submodule "sources/ui5-typescript"]
38 | path = sources/ui5-typescript
39 | url = https://github.com/UI5/typescript.git
40 | branch = gh-pages
41 | [submodule "sources/ui5-cc-spreadsheetimporter"]
42 | path = sources/ui5-cc-spreadsheetimporter
43 | url = https://github.com/spreadsheetimporter/ui5-cc-spreadsheetimporter.git
44 | branch = main
45 | [submodule "sources/abap-cheat-sheets"]
46 | path = sources/abap-cheat-sheets
47 | url = https://github.com/SAP-samples/abap-cheat-sheets.git
48 | branch = main
49 | [submodule "sources/sap-styleguides"]
50 | path = sources/sap-styleguides
51 | url = https://github.com/SAP/styleguides.git
52 | branch = main
53 | [submodule "sources/dsag-abap-leitfaden"]
54 | path = sources/dsag-abap-leitfaden
55 | url = https://github.com/1DSAG/ABAP-Leitfaden.git
56 | branch = main
57 | [submodule "sources/abap-fiori-showcase"]
58 | path = sources/abap-fiori-showcase
59 | url = https://github.com/SAP-samples/abap-platform-fiori-feature-showcase.git
60 | branch = main
61 | [submodule "sources/cap-fiori-showcase"]
62 | path = sources/cap-fiori-showcase
63 | url = https://github.com/SAP-samples/fiori-elements-feature-showcase.git
64 | branch = main
65 | [submodule "sources/abap-docs"]
66 | path = sources/abap-docs
67 | url = https://github.com/marianfoo/abap-docs.git
68 | branch = main
69 |
```
--------------------------------------------------------------------------------
/test/performance/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Performance Benchmarks
2 |
3 | This directory contains performance tests for the hybrid search system.
4 |
5 | ## Reranker Benchmark
6 |
7 | The `reranker-benchmark.js` script compares performance between:
8 | - **BM25-only mode** (`RERANKER_MODEL=""`)
9 | - **BGE reranker mode** (`RERANKER_MODEL="Xenova/bge-reranker-base"`)
10 |
11 | ### Usage
12 |
13 | ```bash
14 | npm run benchmark:reranker
15 | ```
16 |
17 | ### What it measures
18 |
19 | 1. **Startup Time**: How long each server takes to become ready
20 | 2. **Request Times**: Average response time for various queries
21 | 3. **Memory Usage**: RAM consumption for each mode
22 |
23 | ### Test Queries
24 |
25 | The benchmark tests these representative queries:
26 | - `extensionAPI` - Simple term search
27 | - `UI.Chart #SpecificationWidthColumnChart` - Complex annotation query
28 | - `column micro chart sapui5` - Multi-term search
29 | - `Use enums cql cap` - Technical documentation query
30 | - `getting started with sap cloud sdk for ai` - Long descriptive query
31 |
32 | ### Expected Results
33 |
34 | **BM25-only mode:**
35 | - Startup: ~1-2 seconds
36 | - Requests: ~50-150ms average
37 | - Memory: ~200-400MB
38 |
39 | **BGE reranker mode:**
40 | - Startup: ~5-60 seconds (depending on model download)
41 | - Requests: ~200-800ms average
42 | - Memory: ~2-3GB additional
43 |
44 | ### Notes
45 |
46 | - First run with BGE model will be slower due to model download
47 | - Subsequent runs will be faster as model is cached
48 | - Results may vary based on hardware and network conditions
49 | - The benchmark uses different ports (3901, 3902) to avoid conflicts
50 |
```
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # URL Validation Testing Scripts
2 |
3 | This directory contains comprehensive URL validation tools for the SAP Docs MCP server. These tools help ensure that generated documentation URLs are accurate and reachable.
4 |
5 | ## 🚀 Quick Start
6 |
7 | ```bash
8 | # Check URL generation status for all sources
9 | npm run test:urls:status
10 |
11 | # Test random URLs from all sources (comprehensive)
12 | npm run test:urls
13 |
14 | # Quick test of specific sources
15 | npm run test:urls:quick cloud-sdk 2
16 | ```
17 |
18 | ## 📋 Available Scripts
19 |
20 | ### 1. `npm run test:urls` - Comprehensive URL Validation
21 |
22 | **File**: `validate-urls.ts`
23 |
24 | Tests 5 random URLs from each documentation source in parallel. Provides detailed reporting including:
25 | - Success/failure rates by source
26 | - Response times and performance metrics
27 | - Failed URL analysis
28 | - Overall coverage statistics
29 |
30 | ```bash
31 | npm run test:urls
32 | ```
33 |
34 | **Sample Output**:
35 | ```
36 | 🔗 SAP Docs MCP - URL Validation Tool
37 | Testing random URLs from each documentation source...
38 |
39 | 📚 Testing SAPUI5 (/sapui5)
40 | ✅ [200] View Gallery (221ms)
41 | ✅ [200] Ordering (190ms)
42 | Results: ✅ 5 OK, ❌ 0 Failed
43 |
44 | 📊 SUMMARY REPORT
45 | Overall Results:
46 | Total URLs tested: 55
47 | Successful: 34
48 | Failed: 21
49 | Success rate: 61.8%
50 | ```
51 |
52 | ### 2. `npm run test:urls:quick` - Quick Targeted Testing
53 |
54 | **File**: `quick-url-test.ts`
55 |
56 | Fast testing of specific sources with configurable sample size.
57 |
58 | ```bash
59 | # Test 2 URLs from Cloud SDK sources
60 | npm run test:urls:quick cloud-sdk 2
61 |
62 | # Test 5 URLs from CAP
63 | npm run test:urls:quick cap 5
64 |
65 | # Test 1 URL from UI5 sources
66 | npm run test:urls:quick ui5 1
67 |
68 | # Test 3 URLs from all sources (default)
69 | npm run test:urls:quick
70 | ```
71 |
72 | **Sample Output**:
73 | ```
74 | 🔗 Quick URL Test
75 |
76 | 📚 Cloud SDK (JavaScript)
77 | ✅ [200] Getting Started
78 | https://sap.github.io/cloud-sdk/docs/js/getting-started
79 | 215ms
80 | ```
81 |
82 | ### 3. `npm run test:urls:status` - URL Configuration Status
83 |
84 | **File**: `url-status.ts`
85 |
86 | Shows which sources have URL generation configured and provides system overview.
87 |
88 | ```bash
89 | npm run test:urls:status
90 | ```
91 |
92 | **Sample Output**:
93 | ```
94 | 🔗 URL Generation Status
95 |
96 | ✅ Sources with URL generation (11):
97 | ✅ Cloud SDK (JavaScript) (/cloud-sdk-js)
98 | 📄 394 documents
99 | 🌐 https://sap.github.io/cloud-sdk/docs/js
100 |
101 | ❌ Sources without URL generation (1):
102 | ❌ OpenUI5 Samples (/openui5-samples)
103 |
104 | 📊 Summary:
105 | URL generation coverage: 92%
106 | ```
107 |
108 | ## 🎯 Use Cases
109 |
110 | ### Development Workflow
111 | 1. **Check status**: Run `npm run test:urls:status` to see URL configuration coverage
112 | 2. **Quick test**: Use `npm run test:urls:quick` to test specific sources you're working on
113 | 3. **Full validation**: Run `npm run test:urls` before releases to ensure URL accuracy
114 |
115 | ### CI/CD Integration
116 | ```bash
117 | # Add to your CI pipeline
118 | npm run test:urls
119 | if [ $? -ne 0 ]; then
120 | echo "URL validation failed"
121 | exit 1
122 | fi
123 | ```
124 |
125 | ### Debugging URL Issues
126 | ```bash
127 | # Test specific problematic source
128 | npm run test:urls:quick ui5-tooling 5
129 |
130 | # Check if URL generation is configured
131 | npm run test:urls:status
132 | ```
133 |
134 | ## 📊 Understanding Results
135 |
136 | ### Status Codes
137 | - **✅ 200**: URL is valid and reachable
138 | - **❌ 404**: URL not found (indicates URL generation issue)
139 | - **❌ 0**: Network error or timeout
140 |
141 | ### Success Rates by Source Type
142 | - **100%**: Well-configured sources (SAPUI5, CAP, wdi5)
143 | - **60-80%**: Sources with some URL pattern issues (Cloud SDK variants)
144 | - **0%**: Sources with systematic URL generation problems (UI5 Tooling, Web Components)
145 |
146 | ### Performance Metrics
147 | - **<200ms**: Good response time
148 | - **200-400ms**: Acceptable response time
149 | - **>400ms**: Slow response (investigate server or network issues)
150 |
151 | ## 🔧 Features
152 |
153 | ### Parallel Testing
154 | All URL tests run in parallel for maximum speed and efficiency.
155 |
156 | ### Error Handling
157 | - Network timeouts (10 second default)
158 | - Graceful failure handling
159 | - Detailed error reporting
160 |
161 | ### Colored Output
162 | - ✅ Green: Success
163 | - ❌ Red: Failure
164 | - ⚠️ Yellow: Warnings
165 | - 🔵 Blue: Information
166 |
167 | ### Flexible Filtering
168 | - Test specific sources by name or ID
169 | - Configurable sample sizes
170 | - Support for partial name matching
171 |
172 | ## 🚀 Advanced Usage
173 |
174 | ### Custom Source Testing
175 | ```bash
176 | # Test only CAP documentation
177 | npx tsx test/quick-url-test.ts cap 10
178 |
179 | # Test all UI5-related sources
180 | npx tsx test/quick-url-test.ts ui5 3
181 | ```
182 |
183 | ### Programmatic Usage
184 | ```typescript
185 | import { generateDocumentationUrl } from '../src/lib/url-generation/index.js';
186 | import { getDocUrlConfig } from '../src/lib/metadata.js';
187 |
188 | const config = getDocUrlConfig('/cloud-sdk-js');
189 | const url = generateDocumentationUrl('/cloud-sdk-js', 'guides/debug.mdx', content, config);
190 | ```
191 |
192 | ## 🐛 Troubleshooting
193 |
194 | ### Common Issues
195 |
196 | 1. **"Index not found" Error**
197 | ```bash
198 | npm run build
199 | ```
200 |
201 | 2. **High Failure Rate**
202 | - Check internet connection
203 | - Verify URL patterns in `src/metadata.json`
204 | - Check if documentation sites are accessible
205 |
206 | 3. **Timeout Errors**
207 | - Network connectivity issues
208 | - Server temporarily unavailable
209 | - Consider increasing timeout in code
210 |
211 | ### Debugging Steps
212 |
213 | 1. Run status check to see configuration:
214 | ```bash
215 | npm run test:urls:status
216 | ```
217 |
218 | 2. Test a small sample first:
219 | ```bash
220 | npm run test:urls:quick problematic-source 1
221 | ```
222 |
223 | 3. Check URL generation for specific files:
224 | ```bash
225 | # Look at the generated URLs in the output
226 | npm run test:urls:quick source-name 1
227 | ```
228 |
229 | ## 📈 Metrics and Reporting
230 |
231 | The comprehensive test provides detailed metrics:
232 | - **Overall success rate**: Percentage of working URLs
233 | - **Per-source breakdown**: Success rates for each documentation source
234 | - **Performance analysis**: Average and maximum response times
235 | - **Failed URL listing**: Complete list of broken URLs for investigation
236 |
237 | These metrics help identify:
238 | - Sources needing URL pattern fixes
239 | - Documentation sites with accessibility issues
240 | - Performance bottlenecks in URL validation
241 |
242 | ## 🎉 Success Stories
243 |
244 | After implementing this URL validation system:
245 | - **Fixed Cloud SDK URLs**: Correct frontmatter-based URL generation
246 | - **Identified broken patterns**: Found UI5 Tooling URL configuration issues
247 | - **Performance insights**: Average response time of 233ms across all sources
248 | - **Coverage improvement**: 92% of sources now have URL generation configured
```
--------------------------------------------------------------------------------
/src/lib/url-generation/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # URL Generation System
2 |
3 | This directory contains the URL generation system for the SAP Docs MCP server. It provides source-specific URL builders that generate accurate links to documentation based on content metadata and file paths.
4 |
5 | ## Architecture
6 |
7 | The system is organized into source-specific modules with a centralized dispatcher:
8 |
9 | ```
10 | src/lib/url-generation/
11 | ├── index.ts # Main entry point and dispatcher
12 | ├── utils.ts # Common utilities (frontmatter parsing, path handling)
13 | ├── cloud-sdk.ts # SAP Cloud SDK URL generation
14 | ├── sapui5.ts # SAPUI5/OpenUI5 URL generation
15 | ├── cap.ts # SAP CAP URL generation
16 | ├── wdi5.ts # wdi5 testing framework URL generation
17 | └── README.md # This file
18 | ```
19 |
20 | ## Key Features
21 |
22 | ### 1. Frontmatter-Based URL Generation
23 |
24 | The system extracts metadata from document frontmatter to generate accurate URLs:
25 |
26 | ```yaml
27 | ---
28 | id: remote-debugging
29 | title: Remotely debug an application on SAP BTP
30 | slug: debug-guide
31 | ---
32 | ```
33 |
34 | ### 2. Source-Specific Handlers
35 |
36 | Each documentation source has its own URL generation logic:
37 |
38 | - **Cloud SDK**: Uses frontmatter `id` with section-based paths
39 | - **SAPUI5**: Uses topic IDs and API control names
40 | - **CAP**: Uses docsify-style URLs with section handling
41 | - **wdi5**: Uses docsify-style URLs with testing-specific sections
42 |
43 | ### 3. Fallback Mechanism
44 |
45 | If no source-specific handler is available, the system falls back to generic filename-based URL generation.
46 |
47 | ### 4. Anchor Generation
48 |
49 | Automatically detects main headings in content and generates appropriate anchor fragments based on the documentation platform's anchor style.
50 |
51 | ## Usage
52 |
53 | ### Basic Usage
54 |
55 | ```typescript
56 | import { generateDocumentationUrl } from './url-generation/index.js';
57 | import { getDocUrlConfig } from '../metadata.js';
58 |
59 | const config = getDocUrlConfig('/cloud-sdk-js');
60 | const url = generateDocumentationUrl(
61 | '/cloud-sdk-js',
62 | 'guides/debug-remote-app.mdx',
63 | content,
64 | config
65 | );
66 | // Result: https://sap.github.io/cloud-sdk/docs/js/guides/remote-debugging
67 | ```
68 |
69 | ### Using Utilities Directly
70 |
71 | ```typescript
72 | import { parseFrontmatter, extractSectionFromPath, buildUrl } from './url-generation/utils.js';
73 |
74 | // Parse document metadata
75 | const frontmatter = parseFrontmatter(content);
76 |
77 | // Extract section from file path
78 | const section = extractSectionFromPath('guides/tutorial.mdx'); // '/guides/'
79 |
80 | // Build clean URLs
81 | const url = buildUrl('https://example.com', 'docs', 'guides', 'tutorial');
82 | ```
83 |
84 | ## Supported Sources
85 |
86 | ### SAP Cloud SDK (`/cloud-sdk-js`, `/cloud-sdk-java`, `/cloud-sdk-ai-js`, `/cloud-sdk-ai-java`)
87 |
88 | - Uses frontmatter `id` field for URL generation
89 | - Automatically detects sections: guides, features, tutorials, environments
90 | - Example: `https://sap.github.io/cloud-sdk/docs/js/guides/remote-debugging`
91 |
92 | ### SAPUI5 (`/sapui5`, `/openui5-api`, `/openui5-samples`)
93 |
94 | - **SAPUI5 Docs**: Uses topic IDs from frontmatter or filename
95 | - **API Docs**: Uses control/namespace paths
96 | - **Samples**: Uses sample-specific paths
97 | - Example: `https://ui5.sap.com/#/topic/123e4567-e89b-12d3-a456-426614174000`
98 |
99 | ### CAP (`/cap`)
100 |
101 | - Uses docsify-style URLs with `#/` fragments
102 | - Supports frontmatter `id` and `slug` fields
103 | - Handles CDS reference docs, tutorials, and guides
104 | - Example: `https://cap.cloud.sap/docs/#/guides/getting-started`
105 |
106 | ### wdi5 (`/wdi5`)
107 |
108 | - Uses docsify-style URLs for testing documentation
109 | - Handles configuration, selectors, and usage guides
110 | - Example: `https://ui5-community.github.io/wdi5/#/configuration/basic`
111 |
112 | ## Adding New Sources
113 |
114 | To add support for a new documentation source:
115 |
116 | 1. **Create a new source file** (e.g., `my-source.ts`):
117 |
118 | ```typescript
119 | import { parseFrontmatter, buildUrl } from './utils.js';
120 | import { DocUrlConfig } from '../metadata.js';
121 |
122 | export interface MySourceUrlOptions {
123 | relFile: string;
124 | content: string;
125 | config: DocUrlConfig;
126 | libraryId: string;
127 | }
128 |
129 | export function generateMySourceUrl(options: MySourceUrlOptions): string | null {
130 | const { relFile, content, config } = options;
131 | const frontmatter = parseFrontmatter(content);
132 |
133 | // Your URL generation logic here
134 | if (frontmatter.id) {
135 | return buildUrl(config.baseUrl, 'docs', frontmatter.id);
136 | }
137 |
138 | // Fallback logic
139 | return null;
140 | }
141 | ```
142 |
143 | 2. **Register in the main dispatcher** (`index.ts`):
144 |
145 | ```typescript
146 | import { generateMySourceUrl, MySourceUrlOptions } from './my-source.js';
147 |
148 | const sourceGenerators: Record<string, (options: UrlGenerationOptions) => string | null> = {
149 | // ... existing generators
150 | '/my-source': (options) => generateMySourceUrl({
151 | ...options,
152 | libraryId: options.libraryId
153 | } as MySourceUrlOptions),
154 | };
155 | ```
156 |
157 | 3. **Add tests** in `test/url-generation.test.ts`
158 |
159 | 4. **Export functions** at the bottom of `index.ts`
160 |
161 | ## Testing
162 |
163 | The system includes comprehensive tests covering:
164 |
165 | - Utility functions (frontmatter parsing, path handling, URL building)
166 | - Source-specific URL generation
167 | - Main dispatcher functionality
168 | - Error handling
169 |
170 | Run tests with:
171 |
172 | ```bash
173 | npm test test/url-generation.test.ts
174 | ```
175 |
176 | ## Configuration
177 |
178 | URL generation is configured through the metadata system. Each source should have:
179 |
180 | ```json
181 | {
182 | "id": "my-source",
183 | "libraryId": "/my-source",
184 | "baseUrl": "https://docs.example.com",
185 | "pathPattern": "/{file}",
186 | "anchorStyle": "github"
187 | }
188 | ```
189 |
190 | - `baseUrl`: Base URL for the documentation site
191 | - `pathPattern`: Pattern for constructing paths (`{file}` is replaced with filename)
192 | - `anchorStyle`: How to format anchor fragments (`github`, `docsify`, or `custom`)
193 |
194 | ## Best Practices
195 |
196 | 1. **Always use frontmatter when available** - It provides the most reliable URL generation
197 | 2. **Handle multiple content patterns** - Documents may have different structures
198 | 3. **Provide fallbacks** - Always have a fallback for when specialized logic fails
199 | 4. **Test thoroughly** - Include tests for different content patterns and edge cases
200 | 5. **Document special cases** - Add comments for source-specific URL patterns
201 |
202 | ## Troubleshooting
203 |
204 | ### URLs not generating correctly
205 |
206 | 1. Check if the source has proper metadata configuration
207 | 2. Verify frontmatter format in test documents
208 | 3. Ensure the source is registered in the dispatcher
209 | 4. Check console logs for fallback usage messages
210 |
211 | ### Tests failing
212 |
213 | 1. Verify expected URLs match the actual anchor generation behavior
214 | 2. Check that frontmatter parsing handles the content format correctly
215 | 3. Ensure path section extraction works with the file structure
216 |
217 | ### New source not working
218 |
219 | 1. Confirm the source ID matches between metadata and dispatcher
220 | 2. Verify the URL generation function is exported and imported correctly
221 | 3. Check that the function returns non-null values for valid inputs
222 |
223 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # SAP Documentation MCP Server
2 |
3 | A fast, lightweight MCP server that provides unified access to official SAP documentation (SAPUI5, CAP, OpenUI5 APIs & samples, wdi5) using efficient BM25 full-text search.
4 | Use it remotely (hosted URL) or run it locally.
5 |
6 | **Public server (MCP Streamable HTTP)**: https://mcp-sap-docs.marianzeis.de/mcp
7 | **Local Streamable HTTP (default: 3122, configurable via MCP_PORT)**: http://127.0.0.1:3122/mcp
8 | **Local HTTP status**: http://127.0.0.1:3001/status
9 |
10 | ---
11 |
12 | ## Quick start
13 |
14 | <details>
15 | <summary><b>Use the hosted server (recommended)</b></summary>
16 |
17 | Point your MCP client to the Streamable HTTP URL:
18 |
19 | ```
20 | https://mcp-sap-docs.marianzeis.de/mcp
21 | ```
22 |
23 | Verify from a shell:
24 |
25 | ```bash
26 | # Should return JSON with api_last_activity
27 | curl -sS https://mcp-sap-docs.marianzeis.de/status | jq .
28 |
29 | # Should return HTTP 410 with migration info (SSE endpoint deprecated)
30 | curl -i https://mcp-sap-docs.marianzeis.de/sse
31 | ```
32 |
33 | </details>
34 |
35 | <details>
36 | <summary><b>Run it locally (STDIO + local HTTP status + Streamable HTTP)</b></summary>
37 |
38 | ```bash
39 | # From repo root
40 | npm ci
41 | ./setup.sh # execute this script to clone the github documentation submodules
42 | npm run build
43 |
44 | # Start the MCP server (STDIO)
45 | node dist/src/server.js
46 |
47 | # OR start the Streamable HTTP server
48 | npm run start:streamable
49 | ```
50 |
51 | **Local health checks**
52 |
53 | ```bash
54 | # HTTP server
55 | curl -sS http://127.0.0.1:3001/status | jq .
56 |
57 | # Streamable HTTP server (local & deployment default)
58 | curl -sS http://127.0.0.1:3122/health | jq .
59 | ```
60 |
61 | </details>
62 |
63 | ---
64 |
65 | ## What you get
66 |
67 | ### 🔍 **Unified Documentation Search**
68 | - **search** – Search across all official SAP documentation sources with intelligent filtering
69 | - **fetch** – Retrieve complete documents/snippets with smart formatting
70 |
71 | ### 🌐 **Community & Help Portal**
72 | - **sap_community_search** – Real-time SAP Community posts with full content of top 3 results (intelligently truncated to 75k chars if needed)
73 | - **sap_help_search** – Comprehensive search across SAP Help Portal documentation
74 | - **sap_help_get** – Retrieve complete SAP Help pages with metadata (intelligently truncated to 75k chars if needed)
75 |
76 | ---
77 |
78 | ## Connect from your MCP client
79 |
80 | ✅ **Remote URL**: use the public MCP Streamable HTTP endpoint
81 | ✅ **Local/STDIO**: run `node dist/src/server.js` and point the client to a command + args
82 | ✅ **Local/Streamable HTTP**: run `npm run start:streamable` and point the client to `http://127.0.0.1:3122/mcp`
83 |
84 | Below are copy-paste setups for popular clients. Each block has remote, local, and streamable HTTP options.
85 |
86 | ---
87 |
88 | ## Claude (Desktop / Web "Connectors")
89 |
90 | <details>
91 | <summary><b>Remote (recommended) — add a custom connector</b></summary>
92 |
93 | 1. Open Claude Settings → Connectors → Add custom connector
94 | 2. Paste the URL:
95 |
96 | ```
97 | https://mcp-sap-docs.marianzeis.de/mcp
98 | ```
99 |
100 | 3. Save; Claude will use the MCP Streamable HTTP protocol for communication.
101 |
102 | **Docs**: Model Context Protocol ["Connect to Remote MCP Servers"](https://modelcontextprotocol.info/docs/clients/) (shows how Claude connects to MCP servers).
103 |
104 | </details>
105 |
106 | <details>
107 | <summary><b>Local (STDIO) — add a local MCP server</b></summary>
108 |
109 | Point Claude to the command and args:
110 |
111 | ```
112 | command: node
113 | args: ["<absolute-path-to-your-repo>/dist/src/server.js"]
114 | ```
115 |
116 | Claude's [user quickstart](https://modelcontextprotocol.io/docs/tutorials/use-remote-mcp-server) shows how to add local servers by specifying a command/args pair.
117 |
118 | </details>
119 |
120 | <details>
121 | <summary><b>Local (Streamable HTTP) — latest MCP protocol</b></summary>
122 |
123 | For the latest MCP protocol (2025-03-26) with Streamable HTTP support:
124 |
125 | 1. Start the streamable HTTP server:
126 | ```bash
127 | npm run start:streamable
128 | ```
129 |
130 | 2. Add a custom connector with the URL:
131 | ```
132 | http://127.0.0.1:3122/mcp
133 | ```
134 |
135 | This provides better performance and supports the latest MCP features including session management and resumability.
136 |
137 | </details>
138 |
139 | ---
140 |
141 | ## Cursor
142 |
143 | <details>
144 | <summary><b>Remote (MCP Streamable HTTP)</b></summary>
145 |
146 | Create or edit `~/.cursor/mcp.json`:
147 |
148 | ```json
149 | {
150 | "mcpServers": {
151 | "sap-docs-remote": {
152 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
153 | }
154 | }
155 | }
156 | ```
157 |
158 | </details>
159 |
160 | <details>
161 | <summary><b>Local (STDIO)</b></summary>
162 |
163 | `~/.cursor/mcp.json`:
164 |
165 | ```json
166 | {
167 | "mcpServers": {
168 | "sap-docs": {
169 | "command": "node",
170 | "args": ["/absolute/path/to/dist/src/server.js"]
171 | }
172 | }
173 | }
174 | ```
175 |
176 |
177 | </details>
178 |
179 | ---
180 |
181 | ## Eclipse (GitHub Copilot)
182 |
183 | Eclipse users can integrate the SAP Docs MCP server with GitHub Copilot for seamless access to SAP development documentation.
184 |
185 |
186 |
187 | <details>
188 | <summary><b>Remote (recommended) — hosted server</b></summary>
189 |
190 | ### Prerequisites
191 | - **Eclipse Version**: 2024-09 or higher
192 | - **GitHub Copilot Extension**: Latest version from Eclipse Marketplace
193 | - **GitHub Account**: With Copilot access
194 | - **Note**: Full ABAP ADT integration is not yet supported
195 |
196 | ### Configuration Steps
197 |
198 | 1. **Install GitHub Copilot Extension**
199 | - Download from [Eclipse Marketplace](https://marketplace.eclipse.org/content/github-copilot)
200 | - Follow the installation instructions
201 |
202 | 2. **Open MCP Configuration**
203 | - Click the Copilot icon (🤖) in the Eclipse status bar
204 | - Select "Edit preferences" from the menu
205 | - Expand "Copilot Chat" in the left panel
206 | - Click on "MCP"
207 |
208 | 3. **Add SAP Docs MCP Server**
209 | ```json
210 | {
211 | "name": "SAP Docs MCP",
212 | "description": "Comprehensive SAP development documentation with ABAP keyword documentation",
213 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
214 | }
215 | ```
216 |
217 | 4. **Verify Configuration**
218 | - The server should appear in your MCP servers list
219 | - Status should show as "Connected" when active
220 |
221 | ### Using SAP Docs in Eclipse
222 |
223 | Once configured, you can use Copilot Chat in Eclipse with enhanced SAP documentation:
224 |
225 | **Example queries:**
226 | ```
227 | How do I implement a Wizard control in UI5?
228 | What is the syntax for inline declarations in ABAP 7.58?
229 | Show me best practices for RAP development
230 | Find wdi5 testing examples for OData services
231 | ```
232 |
233 | **Available Tools:**
234 | - `search` - **Unified search** for all SAP development (UI5, CAP, ABAP, testing) with intelligent ABAP version filtering
235 | - `fetch` - Retrieve complete documentation for any source
236 | - `sap_community_search` - SAP Community integration
237 | - `sap_help_search` - SAP Help Portal access
238 |
239 | </details>
240 |
241 | <details>
242 | <summary><b>Local setup — for offline use</b></summary>
243 |
244 | ### Local MCP Server Configuration
245 |
246 | ```json
247 | {
248 | "name": "SAP Docs MCP (Local)",
249 | "description": "Local SAP documentation server",
250 | "command": "npm",
251 | "args": ["start"],
252 | "cwd": "/absolute/path/to/your/sap-docs-mcp",
253 | "env": {
254 | "NODE_ENV": "production"
255 | }
256 | }
257 | ```
258 |
259 | **Prerequisites for local setup:**
260 | 1. Clone and build this repository locally
261 | 2. Run `npm run setup` to initialize all documentation sources
262 | 3. Ensure the server starts correctly with `npm start`
263 |
264 | </details>
265 |
266 | ---
267 |
268 | ## VS Code (GitHub Copilot Chat)
269 |
270 | <details>
271 | <summary><b>Remote (recommended) — no setup required</b></summary>
272 |
273 | **Prerequisites**: VS Code 1.102+ with MCP support enabled (enabled by default).
274 |
275 | ### Quick Setup
276 | Create `.vscode/mcp.json` in your workspace:
277 |
278 | ```json
279 | {
280 | "servers": {
281 | "sap-docs": {
282 | "type": "http",
283 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
284 | }
285 | }
286 | }
287 | ```
288 |
289 |
290 | ### Using the Remote Server
291 | 1. Save the `.vscode/mcp.json` file in your workspace
292 | 2. VS Code will automatically detect and start the MCP server
293 | 3. Open Chat view and select **Agent mode**
294 | 4. Click **Tools** button to see available SAP documentation tools
295 | 5. Ask questions like "How do I implement authentication in SAPUI5?"
296 |
297 | **Benefits**:
298 | - ✅ No local installation required
299 | - ✅ Always up-to-date documentation
300 | - ✅ Automatic updates and maintenance
301 | - ✅ Works across all your projects
302 |
303 | **Note**: You'll be prompted to trust the remote MCP server when connecting for the first time.
304 |
305 | </details>
306 |
307 | <details>
308 | <summary><b>Local setup — for offline use</b></summary>
309 |
310 | ### Local STDIO Server
311 | ```json
312 | {
313 | "servers": {
314 | "sap-docs-local": {
315 | "type": "stdio",
316 | "command": "node",
317 | "args": ["<absolute-path>/dist/src/server.js"]
318 | }
319 | }
320 | }
321 | ```
322 |
323 | ### Local HTTP Server
324 | ```json
325 | {
326 | "servers": {
327 | "sap-docs-http": {
328 | "type": "http",
329 | "url": "http://127.0.0.1:3122/mcp"
330 | }
331 | }
332 | }
333 | ```
334 | (Start local server with `npm run start:streamable` first)
335 |
336 | ### Alternative Setup Methods
337 | - **Command Palette**: Run `MCP: Add Server` → choose server type → provide details → select scope
338 | - **User Configuration**: Run `MCP: Open User Configuration` for global setup across all workspaces
339 |
340 | See Microsoft's ["Use MCP servers in VS Code"](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for complete documentation.
341 |
342 | </details>
343 |
344 |
345 | ---
346 |
347 | ## Raycast
348 |
349 | <details>
350 | <summary><b>Remote (MCP Streamable HTTP)</b></summary>
351 |
352 | Open Raycast → Open Command "Manage Servers (MCP) → Import following JSON
353 |
354 | ```json
355 | {
356 | "mcpServers": {
357 | "sap-docs": {
358 | "command": "npx",
359 | "args": ["mcp-remote@latest", "https://mcp-sap-docs.marianzeis.de/mcp"]
360 | }
361 | }
362 | }
363 | ```
364 |
365 | </details>
366 |
367 | <details>
368 | <summary><b>Local (STDIO)</b></summary>
369 |
370 | Open Raycast → Open Command "Manage Servers (MCP) → Import following JSON
371 |
372 | ```json
373 | {
374 | "mcpServers": {
375 | "sap-docs": {
376 | "command": "node",
377 | "args": ["/absolute/path/to/dist/src/server.js"]
378 | }
379 | }
380 | }
381 | ```
382 |
383 | </details>
384 |
385 | Raycast by default asks to confirm each usage of an MCP tool. You can enable automatic confirmation:
386 |
387 | Open Raycast → Raycast Settings → AI → Model Context Protocol → Check "Automatically confirm all tool calls"
388 |
389 | ---
390 |
391 | ## Features
392 |
393 | ### 🔍 Advanced Search Capabilities
394 | - **Unified search** across all official SAP documentation with intelligent ABAP version filtering
395 | - **BM25 full-text search** with SQLite FTS5 for fast, relevant results (~15ms average query time)
396 | - **Context-aware scoring** with automatic stopword filtering and phrase detection
397 | - **Version-specific filtering** - shows latest ABAP by default, specific versions only when requested
398 |
399 | ### 🌐 Real-time External Integration
400 | - **SAP Community**: Full content retrieval using "Best Match" algorithm with engagement metrics
401 | - **SAP Help Portal**: Direct API access to all SAP product documentation (S/4HANA, BTP, Analytics Cloud)
402 | - **Efficient processing**: Batch content retrieval and intelligent caching for fast response times
403 |
404 | ### 💡 Smart Features
405 | - **Automatic content enhancement**: Code highlighting and sample categorization
406 | - **Intelligent ranking**: Context-aware scoring with source-specific weighting
407 | - **Performance optimized**: Lightweight SQLite FTS5 with no external ML dependencies
408 |
409 | ---
410 |
411 | ## What's Included
412 |
413 | This MCP server provides unified access to **comprehensive SAP development documentation** across multiple product areas. All sources are searched simultaneously through the `search` tool, with intelligent filtering and ranking.
414 |
415 | ### 📊 Documentation Coverage Overview
416 |
417 | | Source Category | Sources | File Count | Description |
418 | |-----------------|---------|------------|-------------|
419 | | **ABAP Development** | 4 sources | 40,800+ files | Official ABAP keyword docs (8 versions), cheat sheets, Fiori showcase, community guidelines |
420 | | **UI5 Development** | 6 sources | 12,000+ files | SAPUI5 docs, OpenUI5 APIs/samples, TypeScript, tooling, web components, custom controls |
421 | | **CAP Development** | 2 sources | 250+ files | Cloud Application Programming model docs and Fiori Elements showcase |
422 | | **Cloud & Deployment** | 3 sources | 500+ files | SAP Cloud SDK (JS/Java), Cloud SDK for AI, Cloud MTA Build Tool |
423 | | **Testing & Quality** | 2 sources | 260+ files | wdi5 E2E testing framework, SAP style guides |
424 |
425 | ### 🔍 ABAP Development Sources
426 | - **Official ABAP Keyword Documentation** (`/abap-docs`) - **40,761+ curated ABAP files** across 8 versions (7.52-7.58 + latest) with intelligent version filtering
427 | 📁 **GitHub**: [marianfoo/abap-docs](https://github.com/marianfoo/abap-docs)
428 | - **ABAP Cheat Sheets** (`/abap-cheat-sheets`) - 32 comprehensive cheat sheets covering core ABAP concepts, SQL, OOP, RAP, and more
429 | 📁 **GitHub**: [SAP-samples/abap-cheat-sheets](https://github.com/SAP-samples/abap-cheat-sheets)
430 | - **ABAP RAP Fiori Elements Showcase** (`/abap-fiori-showcase`) - Complete annotation reference for ABAP RESTful Application Programming (RAP)
431 | 📁 **GitHub**: [SAP-samples/abap-platform-fiori-feature-showcase](https://github.com/SAP-samples/abap-platform-fiori-feature-showcase)
432 | - **DSAG ABAP Guidelines** (`/dsag-abap-leitfaden`) - German ABAP community best practices and development standards
433 | 📁 **GitHub**: [1DSAG/ABAP-Leitfaden](https://github.com/1DSAG/ABAP-Leitfaden)
434 |
435 | ### 🎨 UI5 Development Sources
436 | - **SAPUI5 Documentation** (`/sapui5-docs`) - **1,485+ files** - Complete official developer guide, controls, and best practices
437 | 📁 **GitHub**: [SAP-docs/sapui5](https://github.com/SAP-docs/sapui5)
438 | - **OpenUI5 Framework** (`/openui5`) - **20,000+ files** - Complete OpenUI5 source including 500+ control APIs with detailed JSDoc and 2,000+ working examples from demokit samples
439 | 📁 **GitHub**: [SAP/openui5](https://github.com/SAP/openui5)
440 | - **UI5 TypeScript Integration** (`/ui5-typescript`) - Official TypeScript setup guides, type definitions, and migration documentation
441 | 📁 **GitHub**: [UI5/typescript](https://github.com/UI5/typescript)
442 | - **UI5 Tooling** (`/ui5-tooling`) - Complete UI5 Tooling documentation for project setup, build, and development workflows
443 | 📁 **GitHub**: [SAP/ui5-tooling](https://github.com/SAP/ui5-tooling)
444 | - **UI5 Web Components** (`/ui5-webcomponents`) - **4,500+ files** - Comprehensive web components documentation, APIs, and implementation examples
445 | 📁 **GitHub**: [SAP/ui5-webcomponents](https://github.com/SAP/ui5-webcomponents)
446 | - **UI5 Custom Controls** (`/ui5-cc-spreadsheetimporter`) - Spreadsheet importer and other community custom control documentation
447 | 📁 **GitHub**: [spreadsheetimporter/ui5-cc-spreadsheetimporter](https://github.com/spreadsheetimporter/ui5-cc-spreadsheetimporter)
448 |
449 | ### ☁️ CAP Development Sources
450 | - **CAP Documentation** (`/cap-docs`) - **195+ files** - Complete Cloud Application Programming model documentation for Node.js and Java
451 | 📁 **GitHub**: [cap-js/docs](https://github.com/cap-js/docs)
452 | - **CAP Fiori Elements Showcase** (`/cap-fiori-showcase`) - Comprehensive annotation reference and examples for CAP-based Fiori Elements applications
453 | 📁 **GitHub**: [SAP-samples/fiori-elements-feature-showcase](https://github.com/SAP-samples/fiori-elements-feature-showcase)
454 |
455 | ### 🚀 Cloud & Deployment Sources
456 | - **SAP Cloud SDK for JavaScript** (`/cloud-sdk`) - Complete SDK documentation, tutorials, and API references for JavaScript/TypeScript
457 | 📁 **GitHub**: [SAP/cloud-sdk](https://github.com/SAP/cloud-sdk)
458 | - **SAP Cloud SDK for Java** (`/cloud-sdk`) - Comprehensive Java SDK documentation and integration guides
459 | 📁 **GitHub**: [SAP/cloud-sdk](https://github.com/SAP/cloud-sdk)
460 | - **SAP Cloud SDK for AI** (`/cloud-sdk-ai`) - Latest AI capabilities integration documentation for both JavaScript and Java
461 | 📁 **GitHub**: [SAP/ai-sdk](https://github.com/SAP/ai-sdk)
462 | - **Cloud MTA Build Tool** (`/cloud-mta-build-tool`) - Complete documentation for Multi-Target Application development and deployment
463 | 📁 **GitHub**: [SAP/cloud-mta-build-tool](https://github.com/SAP/cloud-mta-build-tool)
464 |
465 | ### ✅ Testing & Quality Sources
466 | - **wdi5 Testing Framework** (`/wdi5`) - **225+ files** - End-to-end testing documentation, setup guides, and real-world examples
467 | 📁 **GitHub**: [ui5-community/wdi5](https://github.com/ui5-community/wdi5)
468 | - **SAP Style Guides** (`/sap-styleguides`) - Official SAP coding standards, clean code practices, and development guidelines
469 | 📁 **GitHub**: [SAP/styleguides](https://github.com/SAP/styleguides)
470 |
471 | ---
472 |
473 | ## Example Prompts
474 |
475 | Try these with any connected MCP client to explore the comprehensive documentation:
476 |
477 | ### 🔍 ABAP Development Queries
478 | **ABAP Keyword Documentation (8 versions with intelligent filtering):**
479 | - "What is the syntax for inline declarations in ABAP 7.58?"
480 | - "How do I use SELECT statements with internal tables in ABAP 7.57?"
481 | - "Show me exception handling with TRY-CATCH in modern ABAP"
482 | - "What are constructor expressions for VALUE and CORRESPONDING?"
483 | - "How do I implement ABAP Unit tests with test doubles?"
484 |
485 | **ABAP Best Practices & Guidelines:**
486 | - "What is Clean ABAP and how do I follow the style guide?"
487 | - "Show me ABAP cheat sheet for internal tables operations"
488 | - "Find DSAG ABAP guidelines for object-oriented programming"
489 | - "How to implement RAP with EML in ABAP for Cloud?"
490 |
491 | ### 🎨 UI5 Development Queries
492 | **SAPUI5 & OpenUI5:**
493 | - "How do I implement authentication in SAPUI5?"
494 | - "Find OpenUI5 button control examples with click handlers"
495 | - "Show me fragment reuse patterns in UI5"
496 | - "What are UI5 model binding best practices?"
497 |
498 | **Modern UI5 Development:**
499 | - "Show me TypeScript setup for UI5 development"
500 | - "How do I configure UI5 Tooling for a new project?"
501 | - "Find UI5 Web Components integration examples"
502 | - "How to implement custom controls with UI5 Web Components?"
503 |
504 | ### ☁️ CAP & Cloud Development
505 | **CAP Framework:**
506 | - "How do I implement CDS views with calculated fields in CAP?"
507 | - "Show me CAP authentication and authorization patterns"
508 | - "Find CAP Node.js service implementation examples"
509 | - "How to handle temporal data in CAP applications?"
510 |
511 | **Cloud SDK & Deployment:**
512 | - "How do I use SAP Cloud SDK for JavaScript with OData?"
513 | - "Show me Cloud SDK for AI integration examples"
514 | - "Find Cloud MTA Build Tool configuration for multi-target apps"
515 | - "How to deploy CAP applications to SAP BTP?"
516 |
517 | ### ✅ Testing & Quality
518 | **Testing Frameworks:**
519 | - "Show me wdi5 testing examples for forms and tables"
520 | - "How do I set up wdi5 for OData service testing?"
521 | - "Find end-to-end testing patterns for Fiori Elements apps"
522 |
523 | **Code Quality:**
524 | - "What are SAP style guide recommendations for JavaScript?"
525 | - "Show me clean code practices for ABAP development"
526 |
527 | ### 🌐 Community & Help Portal
528 | **Community Knowledge (with full content):**
529 | - "Find community examples of OData batch operations with complete implementation"
530 | - "Search for RAP development tips and tricks from the community"
531 | - "What are the latest CAP authentication best practices from the community?"
532 |
533 | **SAP Help Portal:**
534 | - "How to configure S/4HANA Fiori Launchpad?"
535 | - "Find BTP integration documentation for Analytics Cloud"
536 | - "Search for ABAP development best practices in S/4HANA"
537 |
538 | ---
539 |
540 | ## Troubleshooting
541 |
542 | <details>
543 | <summary><b>Claude says it can't connect</b></summary>
544 |
545 | - Make sure you're using the modern MCP Streamable HTTP URL:
546 | `https://mcp-sap-docs.marianzeis.de/mcp` (not /sse, which is deprecated).
547 | - Test MCP endpoint from your machine:
548 |
549 | ```bash
550 | curl -i https://mcp-sap-docs.marianzeis.de/mcp
551 | ```
552 |
553 | You should see JSON indicating MCP protocol support.
554 |
555 | </details>
556 |
557 | <details>
558 | <summary><b>VS Code wizard can't detect the server</b></summary>
559 |
560 | - Try adding it as URL first. If there are connection issues, use your local server via command:
561 | ```
562 | node <absolute-path>/dist/src/server.js
563 | ```
564 |
565 | - Microsoft's ["Add an MCP server"](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) guide shows both URL and command flows.
566 |
567 | </details>
568 |
569 | <details>
570 | <summary><b>Local server runs, but the client can't find it</b></summary>
571 |
572 | - Ensure you're pointing to the built entry:
573 | ```
574 | node dist/src/server.js
575 | ```
576 |
577 | - If using PM2/systemd, confirm it's alive:
578 | ```bash
579 | pm2 status mcp-sap-http
580 | pm2 status mcp-sap-proxy
581 | curl -fsS http://127.0.0.1:3001/status | jq .
582 | curl -fsS http://127.0.0.1:18080/status | jq .
583 | ```
584 |
585 | </details>
586 |
587 | ---
588 |
589 | ## Development
590 |
591 | ### Build Commands
592 | ```bash
593 | npm run build:tsc # Compile TypeScript
594 | npm run build:index # Build search index from sources
595 | npm run build:fts # Build FTS5 database
596 | npm run build # Complete build pipeline (tsc + index + fts)
597 | npm run setup # Complete setup (submodules + build)
598 | ```
599 |
600 | ### Server Commands
601 | ```bash
602 | npm start # Start STDIO MCP server
603 | npm run start:http # Start HTTP status server (port 3001)
604 | npm run start:streamable # Start Streamable HTTP MCP server (port 3122)
605 | ```
606 |
607 | ### Local Setup
608 | ```bash
609 | git clone https://github.com/marianfoo/mcp-sap-docs.git
610 | cd mcp-sap-docs
611 | npm ci # Install dependencies
612 | npm run setup # Enhanced setup (optimized submodules + complete build)
613 | ```
614 |
615 | The build process creates optimized search indices for fast offline access while maintaining real-time connectivity to the SAP Community API.
616 |
617 | ---
618 |
619 | ## Health & Status Monitoring
620 |
621 | ### Public Endpoints
622 | ```bash
623 | # Check server status
624 | curl -sS https://mcp-sap-docs.marianzeis.de/status | jq .
625 |
626 | # Test MCP endpoint
627 | curl -i https://mcp-sap-docs.marianzeis.de/mcp
628 | ```
629 |
630 | ### Local Endpoints
631 | ```bash
632 | # HTTP server status
633 | curl -sS http://127.0.0.1:3001/status | jq .
634 |
635 | # MCP Streamable HTTP server status
636 | curl -sS http://127.0.0.1:3122/health | jq .
637 | ```
638 |
639 | ---
640 |
641 | ## Deployment
642 |
643 | ### Automated Workflows
644 | This project includes dual automated workflows:
645 |
646 | 1. **Main Deployment** (on push to `main` or manual trigger)
647 | - SSH into server and pull latest code
648 | - Run enhanced setup with optimized submodule handling
649 | - Restart all PM2 processes (http, streamable) with health checks
650 |
651 | 2. **Daily Documentation Updates** (4 AM UTC)
652 | - Update all documentation submodules to latest versions
653 | - Rebuild search indices with fresh content using enhanced setup
654 | - Restart services automatically
655 |
656 | ### Manual Updates
657 | Trigger documentation updates anytime via GitHub Actions → "Update Documentation Submodules" workflow.
658 |
659 | ---
660 |
661 | ## Architecture
662 |
663 | - **MCP Server** (Node.js/TypeScript) - Exposes Resources/Tools for SAP docs, community & help portal
664 | - **Streamable HTTP Transport** (Latest MCP spec) - HTTP-based transport with session management and resumability
665 | - **BM25 Search Engine** - SQLite FTS5 with optimized OR-logic queries for fast, relevant results
666 | - **Optimized Submodules** - Shallow, single-branch clones with blob filtering for minimal bandwidth
667 |
668 | ### Technical Stack
669 | - **Search Engine**: BM25 with SQLite FTS5 for fast full-text search with OR logic
670 | - **Performance**: ~15ms average query time with optimized indexing
671 | - **Transport**: Latest MCP protocol with HTTP Streamable transport and session management
672 |
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
```yaml
1 | blank_issues_enabled: true
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "recommendations": [
3 | "ms-vscode.vscode-typescript-next",
4 | "esbenp.prettier-vscode",
5 | "ms-vscode.vscode-json"
6 | ]
7 | }
```
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { SapHelpSearchResult } from "./lib/types.js";
2 |
3 | declare global {
4 | var sapHelpSearchCache: Map<string, SapHelpSearchResult> | undefined;
5 | }
6 |
7 | export {};
```
--------------------------------------------------------------------------------
/test/tools/sap_docs_search/search-cap-docs.js:
--------------------------------------------------------------------------------
```javascript
1 | // CAP documentation test cases
2 | export default [
3 | {
4 | name: 'CAP CQL Enums - Enum definitions',
5 | tool: 'search',
6 | query: 'Use enums cql cap',
7 | expectIncludes: ['/cap/cds/cql']
8 | }
9 | ];
10 |
11 |
12 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ES2022",
5 | "moduleResolution": "Node",
6 | "outDir": "dist",
7 | "rootDir": ".",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true
11 | },
12 | "include": ["src", "scripts"]
13 | }
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "search.exclude": {
3 | "**/sources/**": true,
4 | "**/node_modules/**": true,
5 | "**/dist/**": true,
6 | "**/data/**": true
7 | },
8 | "files.watcherExclude": {
9 | "**/sources/**": true,
10 | "**/dist/**": true,
11 | "**/data/**": true
12 | },
13 | "git.ignoredFolders": [
14 | "sources"
15 | ],
16 | "typescript.preferences.includePackageJsonAutoImports": "off"
17 | }
```
--------------------------------------------------------------------------------
/test/tools/sap_docs_search/search-cloud-sdk-ai.js:
--------------------------------------------------------------------------------
```javascript
1 | // SAP Cloud SDK for AI test cases
2 | export default [
3 | {
4 | name: 'AI SDK error handling doc present',
5 | tool: 'search',
6 | query: 'how to access Error Information in the cloud sdk for ai',
7 | expectIncludes: ['/cloud-sdk-ai-js/error-handling.mdx']
8 | },
9 | {
10 | name: 'AI SDK getting started guide',
11 | tool: 'search',
12 | query: 'getting started with sap cloud sdk for ai',
13 | expectIncludes: ['/cloud-sdk-ai-js/']
14 | },
15 | {
16 | name: 'AI SDK FAQ section',
17 | tool: 'search',
18 | query: 'cloud sdk ai frequently asked questions',
19 | expectIncludes: ['/cloud-sdk-ai-java/faq.mdx']
20 | }
21 | ];
22 |
23 |
24 |
```
--------------------------------------------------------------------------------
/test/tools/sap_docs_search/search-cloud-sdk-js.js:
--------------------------------------------------------------------------------
```javascript
1 | // SAP Cloud SDK (JavaScript) test cases
2 | export default [
3 | {
4 | name: 'Cloud SDK JS remote debug guide present',
5 | tool: 'search',
6 | query: 'debug remote app cloud sdk',
7 | expectIncludes: ['/cloud-sdk-js/guides/debug-remote-app.mdx']
8 | },
9 | {
10 | name: 'Cloud SDK JS getting started',
11 | tool: 'search',
12 | query: 'getting started cloud sdk javascript',
13 | expectIncludes: ['/cloud-sdk-js/']
14 | },
15 | {
16 | name: 'Cloud SDK JS upgrade guide',
17 | tool: 'search',
18 | query: 'cloud sdk javascript upgrade version 4',
19 | expectIncludes: ['/cloud-sdk-js/guides/upgrade-to-version-4.mdx']
20 | }
21 | ];
22 |
23 |
24 |
```
--------------------------------------------------------------------------------
/test/tools/sap_docs_search/search-sapui5-docs.js:
--------------------------------------------------------------------------------
```javascript
1 | // SAPUI5 documentation test cases
2 | export default [
3 | {
4 | name: 'SAPUI5 Column Micro Chart',
5 | tool: 'search',
6 | query: 'column micro chart sapui5',
7 | expectIncludes: ['/sapui5/06_SAP_Fiori_Elements/column-micro-chart-1a4ecb8']
8 | },
9 | {
10 | name: 'SAPUI5 Column Micro Chart',
11 | tool: 'search',
12 | query: 'UI.Chart #SpecificationWidthColumnChart',
13 | expectIncludes: ['/sapui5/06_SAP_Fiori_Elements/column-micro-chart-1a4ecb8']
14 | },
15 | {
16 | name: 'SAPUI5 ExtensionAPI',
17 | tool: 'search',
18 | query: 'extensionAPI',
19 | expectIncludes: ['/sapui5/06_SAP_Fiori_Elements/using-the-extensionapi-bd2994b']
20 | }
21 | ];
22 |
23 |
24 |
```
--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
3 | "name": "io.github.marianfoo/mcp-sap-docs",
4 | "description": "Fast MCP server for unified SAP docs search (SAPUI5, CAP, OpenUI5, wdi5) with BM25 full-text search",
5 | "status": "active",
6 | "repository": {
7 | "url": "https://github.com/marianfoo/mcp-sap-docs",
8 | "source": "github"
9 | },
10 | "version": "0.3.15",
11 | "packages": [
12 | {
13 | "registry_type": "npm",
14 | "registry_base_url": "https://registry.npmjs.org",
15 | "identifier": "mcp-sap-docs",
16 | "version": "0.3.15",
17 | "transport": {
18 | "type": "stdio"
19 | }
20 | }
21 | ]
22 | }
23 |
```
--------------------------------------------------------------------------------
/test/_utils/parseResults.js:
--------------------------------------------------------------------------------
```javascript
1 | // Parse the formatted summary from http-server /mcp response
2 | // Extracts top items and their numeric scores.
3 | export function parseSummaryText(text) {
4 | const items = [];
5 | const lineRe = /^⭐️ \*\*(.+?)\*\* \(Score: ([\d.]+)\)/;
6 | const lines = String(text || '').split('\n');
7 |
8 | for (const line of lines) {
9 | const m = line.match(lineRe);
10 | if (m) {
11 | items.push({
12 | id: m[1],
13 | finalScore: parseFloat(m[2]),
14 | rerankerScore: 0, // Always 0 in BM25-only mode
15 | });
16 | }
17 | }
18 |
19 | const matchCandidates = text.match(/Found (\d+) results/);
20 | const totalCandidates = matchCandidates ? parseInt(matchCandidates[1], 10) : null;
21 |
22 | return { items, totalCandidates };
23 | }
24 |
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | // Enable TypeScript support
6 | typecheck: {
7 | enabled: true
8 | },
9 | // Test environment
10 | environment: 'node',
11 | // Include patterns
12 | include: [
13 | 'test/**/*.test.ts',
14 | 'test/**/*.spec.ts'
15 | ],
16 | // Exclude patterns
17 | exclude: [
18 | 'node_modules',
19 | 'dist',
20 | 'sources'
21 | ],
22 | // Test timeout
23 | testTimeout: 10000,
24 | // Reporter
25 | reporter: ['verbose', 'json'],
26 | // Coverage (optional)
27 | coverage: {
28 | provider: 'v8',
29 | reporter: ['text', 'json', 'html'],
30 | exclude: [
31 | 'node_modules/',
32 | 'test/',
33 | 'dist/',
34 | 'sources/'
35 | ]
36 | }
37 | }
38 | });
39 |
```
--------------------------------------------------------------------------------
/src/lib/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Central configuration for search system
2 | export const CONFIG = {
3 | // Default number of results to return
4 | RETURN_K: Number(process.env.RETURN_K ||30),
5 |
6 | // Database paths
7 | DB_PATH: "dist/data/docs.sqlite",
8 | METADATA_PATH: "src/metadata.json",
9 |
10 | // Search behavior
11 | USE_OR_LOGIC: true, // Use OR logic for better recall in BM25-only mode
12 |
13 | // Excerpt lengths for different search types
14 | EXCERPT_LENGTH_MAIN: 400, // Main search results excerpt length
15 | EXCERPT_LENGTH_COMMUNITY: 600, // Community search results excerpt length
16 |
17 | // Maximum content length for SAP Help and Community full content retrieval
18 | // Limits help prevent token overflow and keep responses manageable (~18,750 tokens)
19 | MAX_CONTENT_LENGTH: 75000, // 75,000 characters
20 | };
21 |
```
--------------------------------------------------------------------------------
/test/tools/search.smoke.js:
--------------------------------------------------------------------------------
```javascript
1 | // Simple smoke test for critical search behaviors
2 | import { startServerHttp, waitForStatus, stopServer, docsSearch } from '../_utils/httpClient.js';
3 | import { parseSummaryText } from '../_utils/parseResults.js';
4 | import assert from 'node:assert/strict';
5 |
6 | const QUERIES = [
7 | { q: 'UI5 column micro chart', expect: /Column Micro Chart|Micro.*Chart/i },
8 | { q: 'CAP CQL enums', expect: /Use enums|CQL/i },
9 | { q: 'Cloud SDK AI getting started', expect: /getting started|AI SDK/i },
10 | { q: 'ExtensionAPI', expect: /ExtensionAPI/i },
11 | ];
12 |
13 | (async () => {
14 | const child = startServerHttp();
15 | try {
16 | await waitForStatus();
17 | for (const { q, expect } of QUERIES) {
18 | const summary = await docsSearch(q);
19 | const { items, totalCandidates } = parseSummaryText(summary);
20 | assert.ok(items.length > 0, `no results for "${q}"`);
21 | assert.ok(expect.test(summary), `expected hint missing in "${q}"`);
22 | // Assert we're in BM25-only mode
23 | assert.ok(items.every(i => i.rerankerScore === 0), 'reranker not zero');
24 | }
25 | console.log('✅ Smoke tests passed');
26 | } finally {
27 | await stopServer(child);
28 | }
29 | })();
30 |
```
--------------------------------------------------------------------------------
/src/lib/url-generation/GenericUrlGenerator.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Generic URL Generator for sources without specialized logic
3 | * Handles ui5-tooling, ui5-webcomponents, cloud-mta-build-tool, etc.
4 | */
5 |
6 | import { BaseUrlGenerator, UrlGenerationContext } from './BaseUrlGenerator.js';
7 | import { FrontmatterData } from './utils.js';
8 |
9 | /**
10 | * Generic URL Generator
11 | * Uses configuration-based URL generation for sources without special requirements
12 | */
13 | export class GenericUrlGenerator extends BaseUrlGenerator {
14 |
15 | protected generateSourceSpecificUrl(context: UrlGenerationContext & {
16 | frontmatter: FrontmatterData;
17 | section: string;
18 | anchor: string | null;
19 | }): string | null {
20 | const identifier = this.getIdentifierFromFrontmatter(context.frontmatter);
21 |
22 | // Use frontmatter ID if available
23 | if (identifier) {
24 | // Use the pathPattern to construct the URL properly
25 | let url = this.config.pathPattern.replace('{file}', identifier);
26 | url = this.config.baseUrl + url;
27 |
28 | // Add anchor if available
29 | if (context.anchor) {
30 | url += this.getSeparator() + context.anchor;
31 | }
32 |
33 | return url;
34 | }
35 |
36 | return null; // Let fallback handle filename-based generation
37 | }
38 | }
39 |
```
--------------------------------------------------------------------------------
/src/lib/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface SearchResult {
2 | library_id: string;
3 | topic: string;
4 | id: string;
5 | title: string;
6 | url?: string;
7 | snippet?: string;
8 | score?: number;
9 | metadata?: Record<string, any>;
10 | // Legacy fields for backward compatibility
11 | description?: string;
12 | totalSnippets?: number;
13 | source?: string; // "docs" | "community" | "help"
14 | postTime?: string; // For community posts
15 | author?: string; // For community posts - author name
16 | likes?: number; // For community posts - number of likes/kudos
17 | tags?: string[]; // For community posts - associated tags
18 | }
19 |
20 | export interface SearchResponse {
21 | results: SearchResult[];
22 | error?: string;
23 | }
24 |
25 | // SAP Help specific types
26 | export interface SapHelpSearchResult {
27 | loio: string;
28 | title: string;
29 | url: string;
30 | productId?: string;
31 | product?: string;
32 | version?: string;
33 | versionId?: string;
34 | language?: string;
35 | snippet?: string;
36 | }
37 |
38 | export interface SapHelpSearchResponse {
39 | data?: {
40 | results?: SapHelpSearchResult[];
41 | };
42 | }
43 |
44 | export interface SapHelpMetadataResponse {
45 | data?: {
46 | deliverable?: {
47 | id: string;
48 | buildNo: string;
49 | };
50 | filePath?: string;
51 | };
52 | }
53 |
54 | export interface SapHelpPageContentResponse {
55 | data?: {
56 | currentPage?: {
57 | t?: string; // title
58 | };
59 | deliverable?: {
60 | title?: string;
61 | };
62 | body?: string;
63 | };
64 | }
```
--------------------------------------------------------------------------------
/scripts/check-version.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | // Simple script to check the version of deployed MCP server
4 | import { readFileSync } from 'fs';
5 | import { fileURLToPath } from 'url';
6 | import { dirname, join } from 'path';
7 |
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = dirname(__filename);
10 |
11 | console.log('🔍 SAP Docs MCP Version Check');
12 | console.log('================================');
13 |
14 | // Check local package.json version
15 | try {
16 | const packagePath = join(__dirname, '../package.json');
17 | const packageInfo = JSON.parse(readFileSync(packagePath, 'utf8'));
18 | console.log(`📦 Package Version: ${packageInfo.version}`);
19 | } catch (error) {
20 | console.log('❌ Could not read package.json');
21 | }
22 |
23 | // Try to check running servers
24 | console.log('\n🌐 Checking Running Servers:');
25 |
26 | const servers = [
27 | { name: 'HTTP Server', port: 3001, path: '/status' },
28 | { name: 'Streamable HTTP', port: 3122, path: '/health' }
29 | ];
30 |
31 | for (const server of servers) {
32 | try {
33 | const response = await fetch(`http://127.0.0.1:${server.port}${server.path}`);
34 | if (response.ok) {
35 | const data = await response.json();
36 | console.log(` ✅ ${server.name}: v${data.version || 'unknown'} (port ${server.port})`);
37 | } else {
38 | console.log(` ❌ ${server.name}: Server error ${response.status}`);
39 | }
40 | } catch (error) {
41 | console.log(` ⚠️ ${server.name}: Not responding (port ${server.port})`);
42 | }
43 | }
44 |
45 | console.log('\n✅ Version check complete');
46 |
```
--------------------------------------------------------------------------------
/test/_utils/httpClient.js:
--------------------------------------------------------------------------------
```javascript
1 | // Simple HTTP client for testing MCP tools via /mcp endpoint
2 | import { spawn } from 'node:child_process';
3 |
4 | // ANSI color codes for logging
5 | const colors = {
6 | reset: '\x1b[0m',
7 | dim: '\x1b[2m',
8 | yellow: '\x1b[33m',
9 | red: '\x1b[31m'
10 | };
11 |
12 | function colorize(text, color) {
13 | return `${colors[color]}${text}${colors.reset}`;
14 | }
15 |
16 | const TEST_PORT = process.env.TEST_MCP_PORT || '43122';
17 | const BASE_URL = `http://127.0.0.1:${TEST_PORT}`;
18 |
19 | async function sleep(ms) {
20 | return new Promise(r => setTimeout(r, ms));
21 | }
22 |
23 | export function startServerHttp() {
24 | return spawn('node', ['dist/src/http-server.js'], {
25 | env: { ...process.env, PORT: TEST_PORT },
26 | stdio: 'ignore'
27 | });
28 | }
29 |
30 | export async function waitForStatus(maxAttempts = 50, delayMs = 200) {
31 | for (let i = 1; i <= maxAttempts; i++) {
32 | try {
33 | const res = await fetch(`${BASE_URL}/status`);
34 | if (res.ok) return await res.json();
35 | } catch (_) {}
36 | await sleep(delayMs);
37 | }
38 | throw new Error(colorize('status endpoint did not become ready in time', 'red'));
39 | }
40 |
41 | export async function stopServer(child) {
42 | try { child?.kill?.('SIGINT'); } catch (_) {}
43 | await sleep(150);
44 | }
45 |
46 | export async function docsSearch(query) {
47 | const res = await fetch(`${BASE_URL}/mcp`, {
48 | method: 'POST',
49 | headers: { 'content-type': 'application/json' },
50 | body: JSON.stringify({ role: 'user', content: String(query) })
51 | });
52 | if (!res.ok) throw new Error(colorize(`http /mcp failed: ${res.status}`, 'red'));
53 | const payload = await res.json();
54 | return payload?.content || '';
55 | }
56 |
57 |
58 |
```
--------------------------------------------------------------------------------
/test-search.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env tsx
2 | // Simple search test script - can be run with: npx tsx test-search.ts [keyword]
3 | import { searchLibraries } from './dist/src/lib/localDocs.js';
4 |
5 | async function testSearch() {
6 | // Get search keyword from command line arguments or use default
7 | const keyword = process.argv[2] || 'wizard';
8 |
9 | console.log(`🔍 Testing search for: "${keyword}"\n`);
10 |
11 | try {
12 | const startTime = Date.now();
13 | const result = await searchLibraries(keyword);
14 | const endTime = Date.now();
15 |
16 | console.log(`⏱️ Search completed in ${endTime - startTime}ms\n`);
17 |
18 | if (result.results.length > 0) {
19 | console.log('✅ Search Results:');
20 | console.log('='.repeat(50));
21 | console.log(result.results[0].description);
22 | console.log('='.repeat(50));
23 |
24 | console.log(`\n📊 Summary:`);
25 | console.log(`- Total snippets: ${result.results[0].totalSnippets || 0}`);
26 | console.log(`- Result length: ${result.results[0].description.length} characters`);
27 |
28 | } else {
29 | console.log('❌ No results found');
30 | if (result.error) {
31 | console.log(`Error: ${result.error}`);
32 | }
33 | }
34 |
35 | } catch (error) {
36 | console.error('❌ Search failed:', error);
37 | process.exit(1);
38 | }
39 | }
40 |
41 | // Usage examples
42 | if (process.argv.length === 2) {
43 | console.log(`
44 | 🎯 SAP Docs Search Test
45 |
46 | Usage: npx tsx test-search.ts [keyword]
47 |
48 | Examples:
49 | npx tsx test-search.ts wizard
50 | npx tsx test-search.ts "cds entity"
51 | npx tsx test-search.ts "wdi5 testing"
52 | npx tsx test-search.ts button
53 | npx tsx test-search.ts annotation
54 | npx tsx test-search.ts service
55 |
56 | Running with default keyword "wizard"...
57 | `);
58 | }
59 |
60 | testSearch().catch(console.error);
```
--------------------------------------------------------------------------------
/test/tools/search.generic.spec.js:
--------------------------------------------------------------------------------
```javascript
1 | // Generic semantic tests that use the harness server and docsSearch helper
2 | import { parseSummaryText } from '../_utils/parseResults.js';
3 |
4 | export default [
5 | {
6 | name: 'UI5 micro chart concept is discoverable',
7 | tool: 'search',
8 | query: 'UI.Chart #SpecificationWidthColumnChart',
9 | validate: async ({ docsSearch }) => {
10 | const summary = await docsSearch('UI.Chart #SpecificationWidthColumnChart');
11 | const txt = String(summary).toLowerCase();
12 | const ok = summary.includes('/sapui5/')
13 | && (txt.includes('chart') || txt.includes('micro'));
14 | return { passed: ok, message: ok ? '' : 'No UI5 chart content in results' };
15 | }
16 | },
17 |
18 | {
19 | name: 'Cloud SDK AI getting started is discoverable',
20 | tool: 'search',
21 | query: 'getting started with sap cloud sdk for ai',
22 | validate: async ({ docsSearch }) => {
23 | const summary = await docsSearch('getting started with sap cloud sdk for ai');
24 | const ok = summary.includes('cloud-sdk-ai')
25 | && /getting|start/i.test(summary);
26 | return { passed: ok, message: ok ? '' : 'No Cloud SDK AI getting started content' };
27 | }
28 | },
29 |
30 | {
31 | name: 'CAP enums query finds enums sections',
32 | tool: 'search',
33 | query: 'Use enums cql cap',
34 | validate: async ({ docsSearch }) => {
35 | const summary = await docsSearch('Use enums cql cap');
36 | const ok = summary.includes('/cap/') && /enum/i.test(summary);
37 | return { passed: ok, message: ok ? '' : 'No CAP enums content' };
38 | }
39 | },
40 |
41 | {
42 | name: 'ExtensionAPI is discoverable in UI5',
43 | tool: 'search',
44 | query: 'extensionAPI',
45 | validate: async ({ docsSearch }) => {
46 | const summary = await docsSearch('extensionAPI');
47 | const ok = summary.includes('/sapui5/') && /extension/i.test(summary);
48 | return { passed: ok, message: ok ? '' : 'No ExtensionAPI content' };
49 | }
50 | }
51 | ];
```
--------------------------------------------------------------------------------
/src/lib/url-generation/dsag.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * DSAG ABAP Leitfaden URL Generator
3 | * Handles GitHub Pages URLs for DSAG ABAP Guidelines
4 | */
5 |
6 | import { BaseUrlGenerator, UrlGenerationContext } from './BaseUrlGenerator.js';
7 | import { FrontmatterData } from './utils.js';
8 |
9 | export interface DsagUrlOptions {
10 | relFile: string;
11 | content: string;
12 | libraryId: string;
13 | }
14 |
15 | /**
16 | * URL Generator for DSAG ABAP Leitfaden
17 | *
18 | * Transforms docs/path/file.md -> /path/file/
19 | * Example: docs/clean-core/what-is-clean-core.md -> https://1dsag.github.io/ABAP-Leitfaden/clean-core/what-is-clean-core/
20 | */
21 | export class DsagUrlGenerator extends BaseUrlGenerator {
22 |
23 | protected generateSourceSpecificUrl(context: UrlGenerationContext & {
24 | frontmatter: FrontmatterData;
25 | section: string;
26 | anchor: string | null;
27 | }): string | null {
28 |
29 | // Transform the relative file path for GitHub Pages
30 | // Remove docs/ prefix and .md extension, add trailing slash
31 | let urlPath = context.relFile;
32 |
33 | // Remove docs/ prefix if present
34 | if (urlPath.startsWith('docs/')) {
35 | urlPath = urlPath.substring(5);
36 | }
37 |
38 | // Remove .md extension
39 | urlPath = urlPath.replace(/\.md$/, '');
40 |
41 | // Build the final URL with trailing slash
42 | let url = `${this.config.baseUrl}/${urlPath}/`;
43 |
44 | // Add anchor if available
45 | if (context.anchor) {
46 | url += '#' + context.anchor;
47 | }
48 |
49 | return url;
50 | }
51 | }
52 |
53 | /**
54 | * Generate DSAG ABAP Leitfaden URL
55 | * @param relFile - Relative file path (e.g., "docs/clean-core/what-is-clean-core.md")
56 | * @param content - File content for extracting anchors
57 | * @returns Generated GitHub Pages URL with proper path transformation
58 | */
59 | export function generateDsagUrl(relFile: string, content: string): string {
60 | const baseUrl = 'https://1dsag.github.io/ABAP-Leitfaden';
61 |
62 | // Transform path: docs/clean-core/what-is-clean-core.md -> clean-core/what-is-clean-core/
63 | let urlPath = relFile;
64 | if (urlPath.startsWith('docs/')) {
65 | urlPath = urlPath.substring(5);
66 | }
67 | urlPath = urlPath.replace(/\.md$/, '');
68 |
69 | return `${baseUrl}/${urlPath}/`;
70 | }
71 |
```
--------------------------------------------------------------------------------
/ecosystem.config.cjs:
--------------------------------------------------------------------------------
```
1 | // PM2 configuration for SAP Docs MCP server
2 | // Modern MCP streamable HTTP transport only (SSE proxy removed)
3 | module.exports = {
4 | apps: [
5 | // HTTP status server on :3001 (pinned port for PM2)
6 | {
7 | name: "mcp-sap-http",
8 | script: "node",
9 | args: ["/opt/mcp-sap/mcp-sap-docs/dist/src/http-server.js"],
10 | cwd: "/opt/mcp-sap/mcp-sap-docs",
11 | env: {
12 | NODE_ENV: "production",
13 | PORT: "3001",
14 | LOG_LEVEL: "DEBUG", // Enhanced for debugging
15 | LOG_FORMAT: "json",
16 | // BM25-only search configuration
17 | RETURN_K: "30" // Centralized result limit (can override CONFIG.RETURN_K)
18 | },
19 | autorestart: true,
20 | max_restarts: 10,
21 | restart_delay: 2000,
22 | // Enhanced logging configuration
23 | log_file: "/opt/mcp-sap/logs/mcp-http-combined.log",
24 | out_file: "/opt/mcp-sap/logs/mcp-http-out.log",
25 | error_file: "/opt/mcp-sap/logs/mcp-http-error.log",
26 | log_type: "json",
27 | log_date_format: "YYYY-MM-DD HH:mm:ss Z",
28 | // Log rotation
29 | max_size: "10M",
30 | retain: 10,
31 | compress: true
32 | },
33 |
34 | // Streamable HTTP MCP server (latest MCP spec)
35 | {
36 | name: "mcp-sap-streamable",
37 | script: "node",
38 | args: ["/opt/mcp-sap/mcp-sap-docs/dist/src/streamable-http-server.js"],
39 | cwd: "/opt/mcp-sap/mcp-sap-docs",
40 | env: {
41 | NODE_ENV: "production",
42 | MCP_PORT: "3122",
43 | LOG_LEVEL: "DEBUG", // Enhanced for debugging
44 | LOG_FORMAT: "json",
45 | // BM25-only search configuration
46 | RETURN_K: "30" // Centralized result limit (can override CONFIG.RETURN_K)
47 | },
48 | autorestart: true,
49 | max_restarts: 10,
50 | restart_delay: 2000,
51 | // Enhanced logging configuration
52 | log_file: "/opt/mcp-sap/logs/mcp-streamable-combined.log",
53 | out_file: "/opt/mcp-sap/logs/mcp-streamable-out.log",
54 | error_file: "/opt/mcp-sap/logs/mcp-streamable-error.log",
55 | log_type: "json",
56 | log_date_format: "YYYY-MM-DD HH:mm:ss Z",
57 | // Log rotation
58 | max_size: "10M",
59 | retain: 10,
60 | compress: true
61 | }
62 | ]
63 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-sap-docs",
3 | "version": "0.3.19",
4 | "type": "module",
5 | "main": "dist/server.js",
6 | "scripts": {
7 | "build:tsc": "tsc",
8 | "build:index": "npm run build:tsc && node dist/scripts/build-index.js",
9 | "build:fts": "npm run build:tsc && node dist/scripts/build-fts.js",
10 | "build": "npm run build:tsc && npm run build:index && npm run build:fts",
11 | "start": "node dist/src/server.js",
12 | "start:http": "node dist/src/http-server.js",
13 | "start:streamable": "node dist/src/streamable-http-server.js",
14 | "inspect": "npx @modelcontextprotocol/inspector",
15 | "test": "npm run test:url-generation && npm run test:integration",
16 | "test:url-generation": "npm run build:tsc && npx vitest run test/comprehensive-url-generation.test.ts test/prompts.test.ts",
17 | "test:url-generation:debug": "npm run build:tsc && DEBUG_TESTS=true npx vitest run test/comprehensive-url-generation.test.ts",
18 | "test:mcp-urls": "npm run build:tsc && npx vitest run test/mcp-search-url-verification.test.ts",
19 | "test:integration": "npm run build && node test/tools/run-tests.js",
20 | "test:integration:urls": "npm run build && node test/tools/run-tests.js --spec search-url-verification.js",
21 | "test:smoke": "node test/tools/search.smoke.js",
22 | "test:community": "npm run build:tsc && node test/community-search.ts",
23 | "test:urls:status": "npm run build:tsc && npx tsx test/url-status.ts",
24 | "check-version": "node scripts/check-version.js",
25 | "setup": "bash setup.sh",
26 | "setup:submodules": "bash -lc 'git submodule sync --recursive && git submodule update --init --recursive --depth 1 && git submodule status --recursive'"
27 | },
28 | "ts-node": {
29 | "esm": true
30 | },
31 | "dependencies": {
32 | "@modelcontextprotocol/sdk": "^1.19.0",
33 | "better-sqlite3": "^12.2.0",
34 | "cors": "^2.8.5",
35 | "express": "^4.21.2",
36 | "fast-glob": "^3.3.2",
37 | "gray-matter": "^4.0.3",
38 | "zod": "^3.23.8"
39 | },
40 | "devDependencies": {
41 | "@types/better-sqlite3": "^7.6.13",
42 | "@types/cors": "^2.8.19",
43 | "@types/express": "^4.17.23",
44 | "@types/node": "^20.11.19",
45 | "ts-node": "^10.9.2",
46 | "typescript": "^5.4.4",
47 | "vitest": "^2.1.5"
48 | }
49 | }
50 |
```
--------------------------------------------------------------------------------
/.github/workflows/test-pr.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Test PR
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 |
8 | jobs:
9 | test:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 | with:
16 | submodules: recursive
17 |
18 | - name: Setup Node.js
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: '18'
22 | cache: 'npm'
23 |
24 | - name: Install dependencies
25 | run: npm ci
26 |
27 | - name: Setup submodules and build with database verification
28 | run: |
29 | export SKIP_NESTED_SUBMODULES=1
30 | bash setup.sh
31 |
32 | # Verify database was built correctly and is not corrupted
33 | DB_PATH="./dist/data/docs.sqlite"
34 | if [ -f "$DB_PATH" ]; then
35 | echo "✅ Database file created: $DB_PATH"
36 | DB_SIZE=$(du -h "$DB_PATH" | cut -f1)
37 | echo "📏 Database size: $DB_SIZE"
38 |
39 | # Test database integrity
40 | if sqlite3 "$DB_PATH" "PRAGMA integrity_check;" | grep -q "ok"; then
41 | echo "✅ Database integrity check passed"
42 | else
43 | echo "❌ Database integrity check failed"
44 | exit 1
45 | fi
46 |
47 | # Test basic FTS functionality
48 | if sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM docs;" | grep -qE '^[1-9][0-9]*$'; then
49 | echo "✅ Database contains indexed documents"
50 | DOCUMENT_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM docs;")
51 | echo "📄 Document count: $DOCUMENT_COUNT"
52 | else
53 | echo "❌ Database appears empty or malformed"
54 | exit 1
55 | fi
56 | else
57 | echo "❌ Database file not found: $DB_PATH"
58 | exit 1
59 | fi
60 |
61 | - name: Run tests with database health check
62 | run: |
63 | # Run the standard tests
64 | npm run test
65 |
66 | # Additional database health verification after tests
67 | echo "==> Post-test database integrity check"
68 | DB_PATH="./dist/data/docs.sqlite"
69 | if sqlite3 "$DB_PATH" "PRAGMA integrity_check;" | grep -q "ok"; then
70 | echo "✅ Database integrity maintained after tests"
71 | else
72 | echo "❌ Database corruption detected after tests"
73 | exit 1
74 | fi
75 |
```
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3 | import { logger } from "./lib/logger.js";
4 | import { BaseServerHandler } from "./lib/BaseServerHandler.js";
5 |
6 | function createServer() {
7 | const serverOptions: NonNullable<ConstructorParameters<typeof Server>[1]> & {
8 | protocolVersions?: string[];
9 | } = {
10 | protocolVersions: ["2025-07-09"],
11 | capabilities: {
12 | // resources: {}, // DISABLED: Causes 60,000+ resources which breaks Cursor
13 | tools: {}, // Enable tools capability
14 | prompts: {} // Enable prompts capability for 2025-07-09 protocol
15 | }
16 | };
17 |
18 | const srv = new Server({
19 | name: "Local SAP Docs",
20 | description:
21 | "Offline SAPUI5 & CAP documentation server with SAP Community, SAP Help Portal, and ABAP Keyword Documentation integration",
22 | version: "0.1.0"
23 | }, serverOptions);
24 |
25 | // Configure server with shared handlers
26 | BaseServerHandler.configureServer(srv);
27 |
28 | return srv;
29 | }
30 |
31 | async function main() {
32 | // Initialize search system with metadata
33 | BaseServerHandler.initializeMetadata();
34 |
35 | const srv = createServer();
36 |
37 | // Log server startup
38 | logger.info("MCP SAP Docs server starting up", {
39 | nodeEnv: process.env.NODE_ENV,
40 | logLevel: process.env.LOG_LEVEL,
41 | logFormat: process.env.LOG_FORMAT
42 | });
43 |
44 | await srv.connect(new StdioServerTransport());
45 | console.error("📚 MCP server ready (stdio) with Tools and Prompts support.");
46 |
47 | // Log successful startup
48 | logger.info("MCP SAP Docs server ready and connected", {
49 | transport: "stdio",
50 | pid: process.pid
51 | });
52 |
53 | // Set up performance monitoring (every 10 minutes for stdio servers)
54 | const performanceInterval = setInterval(() => {
55 | logger.logPerformanceMetrics();
56 | }, 10 * 60 * 1000);
57 |
58 | // Handle server shutdown
59 | process.on('SIGINT', () => {
60 | logger.info('Shutdown signal received, closing stdio server gracefully');
61 | clearInterval(performanceInterval);
62 | logger.info('Stdio server shutdown complete');
63 | process.exit(0);
64 | });
65 |
66 | // Log the port if we're running in HTTP mode (for debugging)
67 | if (process.env.PORT) {
68 | console.error(`📚 MCP server configured for port: ${process.env.PORT}`);
69 | }
70 | }
71 |
72 | main().catch((e) => {
73 | console.error("Fatal:", e);
74 | process.exit(1);
75 | });
76 |
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-documentation-source.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: New Documentation Source Request
2 | description: Suggest a new SAP-related documentation source to be indexed by the MCP server
3 | title: "[NEW SOURCE]: "
4 | assignees: []
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for suggesting a new documentation source! This helps expand our search coverage.
10 |
11 | **Prerequisites:**
12 | - The source must be SAP-related documentation
13 | - **MANDATORY**: The source must be available on GitHub
14 | - The documentation should be publicly accessible
15 | - The content should be searchable and indexable
16 |
17 | - type: input
18 | id: github-repository
19 | attributes:
20 | label: GitHub Repository URL
21 | description: "**REQUIRED**: Provide the full GitHub repository URL"
22 | placeholder: "https://github.com/SAP/some-documentation-repo"
23 | validations:
24 | required: true
25 |
26 | - type: textarea
27 | id: justification
28 | attributes:
29 | label: Why should this source be added?
30 | description: Explain the value this documentation would provide to SAP developers
31 | placeholder: |
32 | This documentation covers important SAP functionality that developers frequently need:
33 | - Key use cases it addresses
34 | - How it complements existing sources
35 | - Developer pain points it would solve
36 | validations:
37 | required: true
38 |
39 | - type: input
40 | id: source-name
41 | attributes:
42 | label: Source Display Name
43 | description: What should this source be called in search results?
44 | placeholder: "SAP Business Application Studio"
45 | validations:
46 | required: true
47 |
48 | - type: input
49 | id: source-id
50 | attributes:
51 | label: Source ID (suggested)
52 | description: Unique identifier for this source (lowercase, kebab-case, starts with /)
53 | placeholder: "/business-application-studio"
54 | validations:
55 | required: false
56 |
57 | - type: textarea
58 | id: source-description
59 | attributes:
60 | label: Source Description
61 | description: Brief description of what this documentation covers
62 | placeholder: "Official documentation for SAP Business Application Studio development tools and features"
63 | validations:
64 | required: true
65 |
66 | - type: input
67 | id: documentation-path
68 | attributes:
69 | label: Documentation Directory Path
70 | description: Path within the repository where documentation files are located
71 | placeholder: "docs/ or documentation/ or README.md"
72 | validations:
73 | required: true
74 |
75 | - type: textarea
76 | id: additional-info
77 | attributes:
78 | label: Additional Information
79 | description: Any other relevant details about this documentation source
80 | validations:
81 | required: false
82 |
83 |
```
--------------------------------------------------------------------------------
/src/lib/url-generation/cloud-sdk.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * URL generation for SAP Cloud SDK documentation sources
3 | * Handles JavaScript, Java, and AI SDK variants
4 | */
5 |
6 | import { BaseUrlGenerator, UrlGenerationContext } from './BaseUrlGenerator.js';
7 | import { FrontmatterData } from './utils.js';
8 | import { DocUrlConfig } from '../metadata.js';
9 |
10 | export interface CloudSdkUrlOptions {
11 | relFile: string;
12 | content: string;
13 | config: DocUrlConfig;
14 | libraryId: string;
15 | }
16 |
17 | /**
18 | * Cloud SDK URL Generator
19 | * Handles JavaScript, Java, and AI SDK variants with specialized URL generation
20 | */
21 | export class CloudSdkUrlGenerator extends BaseUrlGenerator {
22 |
23 | protected generateSourceSpecificUrl(context: UrlGenerationContext & {
24 | frontmatter: FrontmatterData;
25 | section: string;
26 | anchor: string | null;
27 | }): string | null {
28 | const identifier = this.getIdentifierFromFrontmatter(context.frontmatter);
29 |
30 | // Use frontmatter ID if available (preferred method)
31 | if (identifier) {
32 | // Special handling for AI SDK variants
33 | if (this.isAiSdk()) {
34 | return this.buildAiSdkUrl(context.relFile, identifier);
35 | } else {
36 | return this.buildUrl(this.config.baseUrl, context.section, identifier);
37 | }
38 | }
39 |
40 | return null;
41 | }
42 |
43 | /**
44 | * Check if this is an AI SDK variant
45 | */
46 | private isAiSdk(): boolean {
47 | return this.libraryId.includes('-ai-');
48 | }
49 |
50 | /**
51 | * Build AI SDK specific URL with proper section handling
52 | */
53 | private buildAiSdkUrl(relFile: string, identifier: string): string {
54 | // Extract section from the file path for AI SDK
55 | if (this.isInDirectory(relFile, 'langchain')) {
56 | return this.buildUrl(this.config.baseUrl, 'langchain', identifier);
57 | } else if (this.isInDirectory(relFile, 'getting-started')) {
58 | return this.buildUrl(this.config.baseUrl, 'getting-started', identifier);
59 | } else if (this.isInDirectory(relFile, 'examples')) {
60 | return this.buildUrl(this.config.baseUrl, 'examples', identifier);
61 | }
62 |
63 | // Default behavior for other sections
64 | const section = this.extractSection(relFile);
65 | return this.buildUrl(this.config.baseUrl, section, identifier);
66 | }
67 |
68 | /**
69 | * Override section extraction for Cloud SDK specific patterns
70 | */
71 | protected extractSection(relFile: string): string {
72 | // Check for Cloud SDK specific patterns first
73 | if (this.isInDirectory(relFile, 'environments')) {
74 | return '/environments/';
75 | } else if (this.isInDirectory(relFile, 'getting-started')) {
76 | return '/getting-started/';
77 | }
78 |
79 | // Use base implementation for common patterns
80 | return super.extractSection(relFile);
81 | }
82 | }
83 |
84 | // Convenience functions for backward compatibility and external use
85 |
86 | /**
87 | * Generate URL for Cloud SDK documentation using the class-based approach
88 | */
89 | export function generateCloudSdkUrl(options: CloudSdkUrlOptions): string | null {
90 | const generator = new CloudSdkUrlGenerator(options.libraryId, options.config);
91 | return generator.generateUrl(options);
92 | }
93 |
94 | /**
95 | * Generate AI SDK URL (now handled by the main generator)
96 | */
97 | export function generateCloudSdkAiUrl(options: CloudSdkUrlOptions): string | null {
98 | return generateCloudSdkUrl(options);
99 | }
100 |
101 | /**
102 | * Main URL generator dispatcher for all Cloud SDK variants
103 | */
104 | export function generateCloudSdkUrlForLibrary(options: CloudSdkUrlOptions): string | null {
105 | return generateCloudSdkUrl(options);
106 | }
107 |
108 |
```
--------------------------------------------------------------------------------
/test/prompts.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it } from 'vitest';
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import {
4 | GetPromptRequestSchema,
5 | GetPromptResultSchema,
6 | ListPromptsRequestSchema,
7 | ListPromptsResultSchema
8 | } from '@modelcontextprotocol/sdk/types.js';
9 | import { BaseServerHandler } from '../src/lib/BaseServerHandler.js';
10 |
11 | type RequestHandler = (request: unknown, extra?: unknown) => unknown;
12 |
13 | function createTestServer() {
14 | const server = new Server({
15 | name: 'Test Server',
16 | version: '1.0.0'
17 | }, {
18 | capabilities: {
19 | tools: {},
20 | prompts: {}
21 | }
22 | });
23 |
24 | BaseServerHandler.configureServer(server);
25 |
26 | return server;
27 | }
28 |
29 | function getHandler(server: Server, method: string): RequestHandler {
30 | const handlers: Map<string, RequestHandler> = (server as unknown as { _requestHandlers: Map<string, RequestHandler> })._requestHandlers;
31 | const handler = handlers.get(method);
32 | if (!handler) {
33 | throw new Error(`Handler not registered for method: ${method}`);
34 | }
35 | return handler;
36 | }
37 |
38 | describe('Prompt handlers', () => {
39 | it('lists prompts with schema-compliant metadata', async () => {
40 | const server = createTestServer();
41 | const handler = getHandler(server, 'prompts/list');
42 |
43 | const request = ListPromptsRequestSchema.parse({ method: 'prompts/list' });
44 | const result = await handler(request);
45 | const parsed = ListPromptsResultSchema.safeParse(result);
46 |
47 | expect(parsed.success).toBe(true);
48 | if (!parsed.success) return;
49 |
50 | const prompts = parsed.data.prompts;
51 | expect(prompts.length).toBeGreaterThan(0);
52 |
53 | const searchPrompt = prompts.find(prompt => prompt.name === 'sap_search_help');
54 | expect(searchPrompt).toBeDefined();
55 | expect(searchPrompt?.title).toBe('SAP Documentation Search Helper');
56 | expect(searchPrompt?.arguments?.some(arg => arg.name === 'domain')).toBe(true);
57 | });
58 |
59 | it('returns templated prompt content for sap_search_help', async () => {
60 | const server = createTestServer();
61 | const handler = getHandler(server, 'prompts/get');
62 |
63 | const request = GetPromptRequestSchema.parse({
64 | method: 'prompts/get',
65 | params: {
66 | name: 'sap_search_help',
67 | arguments: {
68 | domain: 'SAPUI5',
69 | context: 'routing features'
70 | }
71 | }
72 | });
73 |
74 | const result = await handler(request);
75 | const parsed = GetPromptResultSchema.parse(result);
76 |
77 | expect(parsed.messages.length).toBeGreaterThan(0);
78 | const [message] = parsed.messages;
79 | expect(message.role).toBe('user');
80 | expect(message.content.type).toBe('text');
81 | expect(message.content.text).toContain('SAPUI5');
82 | expect(message.content.text).toContain('routing features');
83 | });
84 |
85 | it('returns default guidance when optional arguments omitted', async () => {
86 | const server = createTestServer();
87 | const handler = getHandler(server, 'prompts/get');
88 |
89 | const request = GetPromptRequestSchema.parse({
90 | method: 'prompts/get',
91 | params: {
92 | name: 'sap_troubleshoot'
93 | }
94 | });
95 |
96 | const result = await handler(request);
97 | const parsed = GetPromptResultSchema.parse(result);
98 |
99 | expect(parsed.description).toBe('Troubleshooting guide for SAP');
100 | const [message] = parsed.messages;
101 | expect(message.role).toBe('user');
102 | expect(message.content.type).toBe('text');
103 | expect(message.content.text).toContain("I'm experiencing an issue with SAP");
104 | });
105 | });
106 |
```
--------------------------------------------------------------------------------
/src/lib/url-generation/wdi5.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * URL generation for wdi5 testing framework documentation
3 | * Handles testing guides, API docs, and examples
4 | */
5 |
6 | import { BaseUrlGenerator, UrlGenerationContext } from './BaseUrlGenerator.js';
7 | import { FrontmatterData } from './utils.js';
8 | import { DocUrlConfig } from '../metadata.js';
9 |
10 | export interface Wdi5UrlOptions {
11 | relFile: string;
12 | content: string;
13 | config: DocUrlConfig;
14 | libraryId: string;
15 | }
16 |
17 | /**
18 | * wdi5 URL Generator
19 | * Handles wdi5 testing framework documentation with docsify-style URLs
20 | */
21 | export class Wdi5UrlGenerator extends BaseUrlGenerator {
22 |
23 | protected generateSourceSpecificUrl(context: UrlGenerationContext & {
24 | frontmatter: FrontmatterData;
25 | section: string;
26 | anchor: string | null;
27 | }): string | null {
28 | const identifier = this.getIdentifierFromFrontmatter(context.frontmatter);
29 |
30 | // Use frontmatter id for docsify-style URLs
31 | if (identifier) {
32 | const section = this.extractWdi5Section(context.relFile);
33 |
34 | if (section) {
35 | return this.buildDocsifyUrl(`${section}/${identifier}`);
36 | }
37 |
38 | return this.buildDocsifyUrl(identifier);
39 | }
40 |
41 | // Fallback to filename-based URL
42 | const section = this.extractWdi5Section(context.relFile);
43 | const fileName = this.getCleanFileName(context.relFile);
44 |
45 | if (section) {
46 | return this.buildDocsifyUrl(`${section}/${fileName}`);
47 | }
48 |
49 | // Simple filename-based URL with docsify fragment
50 | const cleanFileName = fileName.replace(/\//g, '-').toLowerCase();
51 | return this.buildDocsifyUrl(cleanFileName);
52 | }
53 |
54 | /**
55 | * Extract wdi5-specific sections from file path
56 | */
57 | private extractWdi5Section(relFile: string): string {
58 | if (this.isInDirectory(relFile, 'configuration')) {
59 | return 'configuration';
60 | } else if (this.isInDirectory(relFile, 'usage')) {
61 | return 'usage';
62 | } else if (this.isInDirectory(relFile, 'selectors')) {
63 | return 'selectors';
64 | } else if (this.isInDirectory(relFile, 'locators')) {
65 | return 'locators'; // Handle locators separately from selectors
66 | } else if (this.isInDirectory(relFile, 'authentication')) {
67 | return 'authentication';
68 | } else if (this.isInDirectory(relFile, 'plugins')) {
69 | return 'plugins';
70 | } else if (this.isInDirectory(relFile, 'examples')) {
71 | return 'examples';
72 | } else if (this.isInDirectory(relFile, 'migration')) {
73 | return 'migration';
74 | } else if (this.isInDirectory(relFile, 'troubleshooting')) {
75 | return 'troubleshooting';
76 | }
77 |
78 | return '';
79 | }
80 |
81 | /**
82 | * Override to use wdi5-specific section extraction
83 | */
84 | protected extractSection(relFile: string): string {
85 | return this.extractWdi5Section(relFile);
86 | }
87 | }
88 |
89 | // Convenience functions for backward compatibility
90 |
91 | /**
92 | * Generate URL for wdi5 documentation using the class-based approach
93 | */
94 | export function generateWdi5Url(options: Wdi5UrlOptions): string | null {
95 | const generator = new Wdi5UrlGenerator(options.libraryId, options.config);
96 | return generator.generateUrl(options);
97 | }
98 |
99 | /**
100 | * Generate URL for wdi5 configuration documentation
101 | */
102 | export function generateWdi5ConfigUrl(options: Wdi5UrlOptions): string | null {
103 | return generateWdi5Url(options); // Now handled by the main generator
104 | }
105 |
106 | /**
107 | * Generate URL for wdi5 selector and locator documentation
108 | */
109 | export function generateWdi5SelectorUrl(options: Wdi5UrlOptions): string | null {
110 | return generateWdi5Url(options); // Now handled by the main generator
111 | }
112 |
113 |
```
--------------------------------------------------------------------------------
/src/lib/url-generation/cap.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * URL generation for SAP CAP (Cloud Application Programming) documentation
3 | * Handles CDS guides, reference docs, and tutorials
4 | */
5 |
6 | import { BaseUrlGenerator, UrlGenerationContext } from './BaseUrlGenerator.js';
7 | import { FrontmatterData } from './utils.js';
8 | import { DocUrlConfig } from '../metadata.js';
9 |
10 | export interface CapUrlOptions {
11 | relFile: string;
12 | content: string;
13 | config: DocUrlConfig;
14 | libraryId: string;
15 | }
16 |
17 | /**
18 | * CAP URL Generator
19 | * Handles CDS guides, reference docs, and tutorials with docsify-style URLs
20 | */
21 | export class CapUrlGenerator extends BaseUrlGenerator {
22 |
23 | protected generateSourceSpecificUrl(context: UrlGenerationContext & {
24 | frontmatter: FrontmatterData;
25 | section: string;
26 | anchor: string | null;
27 | }): string | null {
28 | const identifier = this.getIdentifierFromFrontmatter(context.frontmatter);
29 |
30 | // Use frontmatter slug or id for URL generation
31 | if (identifier) {
32 | const section = this.extractCapSection(context.relFile);
33 |
34 | if (section) {
35 | return this.buildDocsifyUrl(`${section}/${identifier}`);
36 | }
37 |
38 | return this.buildDocsifyUrl(identifier);
39 | }
40 |
41 | // Fallback to filename-based URL
42 | const fileName = this.getCleanFileName(context.relFile);
43 | const section = this.extractCapSection(context.relFile);
44 |
45 | if (section) {
46 | return this.buildDocsifyUrl(`${section}/${fileName}`);
47 | }
48 |
49 | return this.buildDocsifyUrl(fileName);
50 | }
51 |
52 | /**
53 | * Extract CAP-specific sections from file path
54 | */
55 | private extractCapSection(relFile: string): string {
56 | if (this.isInDirectory(relFile, 'guides')) {
57 | return 'guides';
58 | } else if (this.isInDirectory(relFile, 'cds')) {
59 | return 'cds';
60 | } else if (this.isInDirectory(relFile, 'node.js')) {
61 | return 'node.js';
62 | } else if (this.isInDirectory(relFile, 'java')) {
63 | return 'java';
64 | } else if (this.isInDirectory(relFile, 'plugins')) {
65 | return 'plugins';
66 | } else if (this.isInDirectory(relFile, 'advanced')) {
67 | return 'advanced';
68 | } else if (this.isInDirectory(relFile, 'get-started')) {
69 | return 'get-started';
70 | } else if (this.isInDirectory(relFile, 'tutorials')) {
71 | return 'tutorials';
72 | }
73 |
74 | return '';
75 | }
76 |
77 | /**
78 | * Override to use CAP-specific section extraction
79 | */
80 | protected extractSection(relFile: string): string {
81 | return this.extractCapSection(relFile);
82 | }
83 |
84 | /**
85 | * Override to use CAP-specific docsify URL building
86 | * CAP URLs have a /docs/ prefix before the # fragment
87 | */
88 | protected buildDocsifyUrl(path: string): string {
89 | const cleanPath = path.startsWith('/') ? path.slice(1) : path;
90 | return `${this.config.baseUrl}/docs/#/${cleanPath}`;
91 | }
92 | }
93 |
94 | // Convenience functions for backward compatibility
95 |
96 | /**
97 | * Generate URL for CAP documentation using the class-based approach
98 | */
99 | export function generateCapUrl(options: CapUrlOptions): string | null {
100 | const generator = new CapUrlGenerator(options.libraryId, options.config);
101 | return generator.generateUrl(options);
102 | }
103 |
104 | /**
105 | * Generate URL for CAP CDS reference documentation
106 | */
107 | export function generateCapCdsUrl(options: CapUrlOptions): string | null {
108 | return generateCapUrl(options); // Now handled by the main generator
109 | }
110 |
111 | /**
112 | * Generate URL for CAP tutorials and getting started guides
113 | */
114 | export function generateCapTutorialUrl(options: CapUrlOptions): string | null {
115 | return generateCapUrl(options); // Now handled by the main generator
116 | }
117 |
118 |
```
--------------------------------------------------------------------------------
/src/lib/url-generation/abap.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * URL Generator for ABAP Keyword Documentation
3 | * Maps individual .md files to official SAP ABAP documentation URLs
4 | */
5 |
6 | import { BaseUrlGenerator } from './BaseUrlGenerator.js';
7 | import { DocUrlConfig } from '../metadata.js';
8 |
9 | /**
10 | * ABAP URL Generator for official SAP documentation
11 | * Converts .md filenames to proper help.sap.com URLs
12 | */
13 | export class AbapUrlGenerator extends BaseUrlGenerator {
14 |
15 | generateSourceSpecificUrl(context: any): string | null {
16 |
17 | // Extract filename without extension
18 | let filename = context.relFile.replace(/\.md$/, '');
19 |
20 | // Remove 'md/' prefix if present (from sources/abap-docs/docs/7.58/md/)
21 | filename = filename.replace(/^md\//, '');
22 |
23 | // Convert .md filename back to .html for SAP documentation
24 | const htmlFile = filename + '.html';
25 |
26 | // Get version from config or default to latest (which now points to cloud version)
27 | const version = this.extractVersion() || 'latest';
28 |
29 | // Build SAP help URL
30 | const baseUrl = this.getAbapBaseUrl(version);
31 | const fullUrl = `${baseUrl}/${htmlFile}`;
32 |
33 | // Add anchor if provided
34 | return context.anchor ? `${fullUrl}#${context.anchor}` : fullUrl;
35 | }
36 |
37 | /**
38 | * Extract ABAP version from config
39 | */
40 | private extractVersion(): string | null {
41 | // Check if version is in the library ID or path pattern
42 | const pathPattern = this.config.pathPattern || '';
43 | const libraryId = this.libraryId || '';
44 |
45 | // Handle "latest" version explicitly
46 | if (libraryId.includes('latest') || pathPattern.includes('latest')) {
47 | return 'latest';
48 | }
49 |
50 | // Try to extract version patterns: 7.58, 9.16, 8.10, etc.
51 | const versionMatch = (libraryId + pathPattern).match(/\/(\d+\.\d+)\//);
52 | if (versionMatch) {
53 | return versionMatch[1];
54 | }
55 |
56 | // Try alternative patterns for cloud/new versions
57 | const cloudMatch = (libraryId + pathPattern).match(/-(latest|cloud|916|916\w*|81\w*)-/);
58 | if (cloudMatch) {
59 | const match = cloudMatch[1];
60 | if (match === 'latest' || match === 'cloud') return 'latest';
61 | if (match.startsWith('916')) return '9.16';
62 | if (match.startsWith('81')) return '8.10';
63 | }
64 |
65 | return null;
66 | }
67 |
68 | /**
69 | * Get base URL for ABAP documentation based on version
70 | */
71 | private getAbapBaseUrl(version: string): string {
72 | // Handle latest version - use the newest cloud version
73 | if (version === 'latest') {
74 | return 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US';
75 | }
76 |
77 | const versionNum = parseFloat(version);
78 |
79 | // Cloud versions (9.1x) - ABAP Cloud / SAP BTP
80 | if (versionNum >= 9.1) {
81 | return 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US';
82 | }
83 |
84 | // S/4HANA 2025 versions (8.1x)
85 | if (versionNum >= 8.1) {
86 | // Use the cloud pattern for S/4HANA 2025 as well, since they share the same doc structure
87 | return 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US';
88 | }
89 |
90 | // Legacy versions (7.x) - keep existing pattern
91 | const versionCode = version.replace('.', '');
92 | return `https://help.sap.com/doc/abapdocu_${versionCode}_index_htm/${version}/en-US`;
93 | }
94 | }
95 |
96 | /**
97 | * Generate ABAP documentation URL
98 | */
99 | export function generateAbapUrl(libraryId: string, relativeFile: string, config: DocUrlConfig, anchor?: string): string | null {
100 | const generator = new AbapUrlGenerator(libraryId, config);
101 | return generator.generateSourceSpecificUrl({
102 | relFile: relativeFile,
103 | content: '',
104 | config,
105 | libraryId,
106 | anchor
107 | });
108 | }
109 |
```
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # SAP Documentation MCP Server Setup Script
4 | echo "🚀 Setting up SAP Documentation MCP Server..."
5 |
6 | # Install dependencies
7 | echo "📦 Installing dependencies..."
8 | npm install
9 |
10 | # Initialize and update git submodules
11 | echo "📚 Initializing documentation submodules..."
12 |
13 | # Initialize/update submodules (including new ones) to latest
14 | echo " → Syncing submodule configuration..."
15 | git submodule sync --recursive
16 |
17 | # Collect submodules from .gitmodules
18 | echo " → Ensuring all top-level submodules are present (shallow, single branch)..."
19 | while IFS= read -r line; do
20 | name=$(echo "$line" | awk '{print $1}' | sed -E 's/^submodule\.([^ ]*)\.path$/\1/')
21 | path=$(echo "$line" | awk '{print $2}')
22 | branch=$(git config -f .gitmodules "submodule.${name}.branch" || echo main)
23 | url=$(git config -f .gitmodules "submodule.${name}.url")
24 |
25 | # Skip if missing required fields
26 | [ -z "$path" ] && continue
27 | [ -z "$url" ] && continue
28 |
29 | echo " • $path (branch: $branch)"
30 |
31 | if [ ! -d "$path/.git" ]; then
32 | echo " - cloning shallow..."
33 | GIT_LFS_SKIP_SMUDGE=1 git clone --filter=blob:none --no-tags --single-branch --depth 1 --branch "$branch" "$url" "$path" || {
34 | echo " ! clone failed for $path, retrying with master"
35 | GIT_LFS_SKIP_SMUDGE=1 git clone --filter=blob:none --no-tags --single-branch --depth 1 --branch master "$url" "$path" || true
36 | }
37 | else
38 | echo " - updating shallow to latest $branch..."
39 | # Limit origin to a single branch and fetch shallow
40 | git -C "$path" config --unset-all remote.origin.fetch >/dev/null 2>&1 || true
41 | git -C "$path" config remote.origin.fetch "+refs/heads/${branch}:refs/remotes/origin/${branch}"
42 | git -C "$path" remote set-branches origin "$branch" || true
43 | # configure partial clone + no-tags for smaller fetches
44 | git -C "$path" config remote.origin.tagOpt --no-tags || true
45 | git -C "$path" config remote.origin.promisor true || true
46 | git -C "$path" config remote.origin.partialclonefilter blob:none || true
47 | if ! GIT_LFS_SKIP_SMUDGE=1 git -C "$path" fetch --filter=blob:none --no-tags --depth 1 --prune origin "$branch"; then
48 | echo " ! fetch failed for $branch, trying master"
49 | git -C "$path" config remote.origin.fetch "+refs/heads/master:refs/remotes/origin/master"
50 | git -C "$path" remote set-branches origin master || true
51 | GIT_LFS_SKIP_SMUDGE=1 git -C "$path" fetch --filter=blob:none --no-tags --depth 1 --prune origin master || true
52 | branch=master
53 | fi
54 | # Checkout/reset to the fetched tip
55 | git -C "$path" checkout -B "$branch" "origin/$branch" 2>/dev/null || git -C "$path" checkout "$branch" || true
56 | git -C "$path" reset --hard "origin/$branch" 2>/dev/null || true
57 | # Compact local repository storage (keeps only the shallow pack)
58 | git -C "$path" reflog expire --expire=now --all >/dev/null 2>&1 || true
59 | git -C "$path" gc --prune=now --aggressive >/dev/null 2>&1 || true
60 | fi
61 | done < <(git config -f .gitmodules --get-regexp 'submodule\..*\.path')
62 |
63 | if [ -n "$SKIP_NESTED_SUBMODULES" ]; then
64 | echo " → Skipping nested submodule initialization (SKIP_NESTED_SUBMODULES=1)"
65 | else
66 | echo " → Initializing nested submodules to pinned commits (shallow)..."
67 | git submodule update --init --recursive --depth 1 || true
68 | fi
69 |
70 | echo " → Current submodule status:"
71 | git submodule status --recursive || true
72 |
73 | # Build the search index
74 | echo "🔍 Building search index..."
75 | npm run build
76 |
77 | echo "✅ Setup complete!"
78 | echo ""
79 | echo "To start the MCP server:"
80 | echo " npm start"
81 | echo ""
82 | echo "To use in Cursor:"
83 | echo "1. Open Cursor IDE"
84 | echo "2. Go to Tools → Add MCP Server"
85 | echo "3. Use command: npm start"
86 | echo "4. Set working directory to: $(pwd)"
```
--------------------------------------------------------------------------------
/src/lib/search.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Simple BM25-only search using FTS5 with metadata-driven configuration
2 | import { searchFTS } from "./searchDb.js";
3 | import { CONFIG } from "./config.js";
4 | import { loadMetadata, getSourceBoosts, expandQueryTerms, getAllLibraryMappings } from "./metadata.js";
5 |
6 | export type SearchResult = {
7 | id: string;
8 | text: string;
9 | bm25: number;
10 | sourceId: string;
11 | path: string;
12 | finalScore: number;
13 | };
14 |
15 | // Helper to extract source ID from library_id or document path using metadata
16 | function extractSourceId(libraryIdOrPath: string): string {
17 | if (libraryIdOrPath.startsWith('/')) {
18 | const parts = libraryIdOrPath.split('/');
19 | if (parts.length > 1) {
20 | const sourceId = parts[1];
21 | // Use metadata-driven library mappings
22 | const mappings = getAllLibraryMappings();
23 | return mappings[sourceId] || sourceId;
24 | }
25 | }
26 | return libraryIdOrPath;
27 | }
28 |
29 | export async function search(
30 | query: string,
31 | { k = CONFIG.RETURN_K } = {}
32 | ): Promise<SearchResult[]> {
33 | // Load metadata for boosts and query expansion
34 | loadMetadata();
35 | const sourceBoosts = getSourceBoosts();
36 |
37 | // Expand query with synonyms and acronyms
38 | const queryVariants = expandQueryTerms(query);
39 | const seen = new Map<string, any>();
40 |
41 | // Check if query contains specific ABAP version
42 | const versionMatch = query.match(/\b(7\.\d{2}|latest)\b/i);
43 | const requestedVersion = versionMatch ? versionMatch[1].toLowerCase() : null;
44 | const requestedVersionId = requestedVersion ? requestedVersion.replace('.', '') : null;
45 |
46 | // Search with all query variants (union approach)
47 | for (const variant of queryVariants) {
48 | try {
49 | const rows = searchFTS(variant, {}, k);
50 | for (const r of rows) {
51 | if (!seen.has(r.id)) {
52 | seen.set(r.id, r);
53 | }
54 | }
55 | } catch (error) {
56 | console.warn(`FTS query failed for variant "${variant}":`, error);
57 | continue;
58 | }
59 | if (seen.size >= k) break; // enough candidates
60 | }
61 |
62 | let rows = Array.from(seen.values()).slice(0, k);
63 |
64 | // Smart ABAP version filtering - only show latest unless version specified
65 | if (!requestedVersion) {
66 | // For general ABAP queries without version, aggressively filter out older versions
67 | rows = rows.filter(r => {
68 | const id = r.id || '';
69 |
70 | // Keep all non-ABAP-docs sources
71 | if (!id.includes('/abap-docs-')) return true;
72 |
73 | // For ABAP docs, ONLY keep latest version for general queries
74 | return id.includes('/abap-docs-latest/');
75 | });
76 |
77 | console.log(`Filtered to latest ABAP version only: ${rows.length} results`);
78 | } else {
79 | // For version-specific queries, ONLY show the requested version and non-ABAP sources
80 | rows = rows.filter(r => {
81 | const id = r.id || '';
82 |
83 | // Keep all non-ABAP-docs sources (style guides, cheat sheets, etc.)
84 | if (!id.includes('/abap-docs-')) return true;
85 |
86 | // For ABAP docs, ONLY keep the specifically requested version
87 | return id.includes(`/abap-docs-${requestedVersionId}/`);
88 | });
89 |
90 | console.log(`Filtered to ABAP version ${requestedVersion} only: ${rows.length} results`);
91 | }
92 |
93 | // Convert to consistent format with source boosts
94 | const results = rows.map(r => {
95 | const sourceId = extractSourceId(r.libraryId || r.id);
96 | let boost = sourceBoosts[sourceId] || 0;
97 |
98 | // Additional boost for version-specific queries
99 | if (requestedVersionId && r.id.includes(`/abap-docs-${requestedVersionId}/`)) {
100 | boost += 1.0; // Extra boost for requested version
101 | }
102 |
103 | return {
104 | id: r.id,
105 | text: `${r.title || ""}\n\n${r.description || ""}\n\n${r.id}`,
106 | bm25: r.bm25Score,
107 | sourceId,
108 | path: r.id,
109 | finalScore: (-r.bm25Score) * (1 + boost) // Convert to descending with boost
110 | };
111 | });
112 |
113 | // Results are already filtered above, just sort them
114 |
115 | // Sort by final score (higher = better)
116 | return results.sort((a, b) => b.finalScore - a.finalScore);
117 | }
118 |
```
--------------------------------------------------------------------------------
/scripts/build-fts.ts:
--------------------------------------------------------------------------------
```typescript
1 | // Build pipeline step 2: Compiles dist/data/index.json into dist/data/docs.sqlite/FTS5 for fast search
2 | import fs from "fs";
3 | import path from "path";
4 | import Database from "better-sqlite3";
5 |
6 | type Doc = {
7 | id: string;
8 | relFile: string;
9 | title: string;
10 | description: string;
11 | snippetCount: number;
12 | type?: string;
13 | controlName?: string;
14 | namespace?: string;
15 | keywords?: string[];
16 | properties?: string[];
17 | events?: string[];
18 | aggregations?: string[];
19 | };
20 |
21 | type LibraryBundle = {
22 | id: string; // "/sapui5" | "/cap" | "/openui5-api" | "/openui5-samples" | "/wdi5"
23 | name: string;
24 | description: string;
25 | docs: Doc[];
26 | };
27 |
28 | const DATA_DIR = path.join(process.cwd(), "dist", "data");
29 | const SRC = path.join(DATA_DIR, "index.json");
30 | const DST = path.join(DATA_DIR, "docs.sqlite");
31 |
32 | function libFromId(id: string): string {
33 | // id looks like "/sapui5/..." etc.
34 | const m = id.match(/^\/[^/]+/);
35 | return m ? m[0] : "";
36 | }
37 |
38 | function safeText(x: unknown): string {
39 | if (!x) return "";
40 | if (Array.isArray(x)) return x.join(" ");
41 | return String(x);
42 | }
43 |
44 | function main() {
45 | if (!fs.existsSync(SRC)) {
46 | throw new Error(`Missing ${SRC}. Run npm run build:index first.`);
47 | }
48 |
49 | console.log(`📖 Reading index from ${SRC}...`);
50 | const raw = JSON.parse(fs.readFileSync(SRC, "utf8")) as Record<string, LibraryBundle>;
51 |
52 | // Fresh DB
53 | if (fs.existsSync(DST)) {
54 | console.log(`🗑️ Removing existing ${DST}...`);
55 | fs.unlinkSync(DST);
56 | }
57 |
58 | console.log(`🏗️ Creating FTS5 database at ${DST}...`);
59 | const db = new Database(DST);
60 | db.pragma("journal_mode = WAL");
61 | db.pragma("synchronous = NORMAL");
62 | db.pragma("temp_store = MEMORY");
63 |
64 | // FTS5 schema: columns you want to search get indexed; metadata can be UNINDEXED
65 | // We keep this simple - FTS is just for fast candidate filtering
66 | db.exec(`
67 | CREATE VIRTUAL TABLE docs USING fts5(
68 | libraryId, -- indexed for filtering
69 | type, -- markdown/jsdoc/sample (indexed for filtering)
70 | title, -- strong signal for search
71 | description, -- weaker signal for search
72 | keywords, -- control tags and properties
73 | controlName, -- e.g., Wizard, Button
74 | namespace, -- e.g., sap.m, sap.f
75 | id UNINDEXED, -- metadata (full path id)
76 | relFile UNINDEXED, -- metadata
77 | snippetCount UNINDEXED -- metadata
78 | );
79 | `);
80 |
81 | console.log(`📝 Inserting documents into FTS5 index...`);
82 | const ins = db.prepare(`
83 | INSERT INTO docs (libraryId,type,title,description,keywords,controlName,namespace,id,relFile,snippetCount)
84 | VALUES (?,?,?,?,?,?,?,?,?,?)
85 | `);
86 |
87 | let totalDocs = 0;
88 | const tx = db.transaction(() => {
89 | for (const lib of Object.values(raw)) {
90 | for (const d of lib.docs) {
91 | const libraryId = libFromId(d.id);
92 | const keywords = safeText(d.keywords);
93 | const props = safeText(d.properties);
94 | const events = safeText(d.events);
95 | const aggs = safeText(d.aggregations);
96 |
97 | // Combine all searchable keywords
98 | const keywordsAll = [keywords, props, events, aggs].filter(Boolean).join(" ");
99 |
100 | ins.run(
101 | libraryId,
102 | d.type ?? "",
103 | safeText(d.title),
104 | safeText(d.description),
105 | keywordsAll,
106 | safeText(d.controlName),
107 | safeText(d.namespace),
108 | d.id,
109 | d.relFile,
110 | d.snippetCount ?? 0
111 | );
112 | totalDocs++;
113 | }
114 | }
115 | });
116 |
117 | tx();
118 |
119 | console.log(`📊 Optimizing FTS5 index...`);
120 | db.pragma("optimize");
121 |
122 | // Get some stats
123 | const rowCount = db.prepare("SELECT count(*) as n FROM docs").get() as { n: number };
124 |
125 | db.close();
126 |
127 | console.log(`✅ FTS5 index built successfully!`);
128 | console.log(` 📄 Documents indexed: ${totalDocs}`);
129 | console.log(` 📄 Rows in FTS table: ${rowCount.n}`);
130 | console.log(` 💾 Database size: ${(fs.statSync(DST).size / 1024 / 1024).toFixed(2)} MB`);
131 | console.log(` 📍 Location: ${DST}`);
132 | }
133 |
134 | // ES module equivalent of require.main === module
135 | import { fileURLToPath } from 'url';
136 | if (import.meta.url === `file://${process.argv[1]}`) {
137 | try {
138 | main();
139 | } catch (error) {
140 | console.error("❌ Error building FTS index:", error);
141 | process.exit(1);
142 | }
143 | }
```
--------------------------------------------------------------------------------
/test/quick-url-test.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | /**
3 | * Quick URL Test - Simple version for testing a few URLs from specific sources
4 | */
5 |
6 | import { fileURLToPath } from 'url';
7 | import { dirname, join } from 'path';
8 | import fs from 'fs/promises';
9 | import { existsSync } from 'fs';
10 | import { generateDocumentationUrl } from '../src/lib/url-generation/index.js';
11 | import { getDocUrlConfig, getSourcePath } from '../src/lib/metadata.js';
12 |
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = dirname(__filename);
15 | const PROJECT_ROOT = join(__dirname, '..');
16 | const DATA_DIR = join(PROJECT_ROOT, 'dist', 'data');
17 |
18 | // Simple colors
19 | const c = {
20 | green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
21 | blue: '\x1b[34m', bold: '\x1b[1m', reset: '\x1b[0m'
22 | };
23 |
24 | async function loadIndex() {
25 | const indexPath = join(DATA_DIR, 'index.json');
26 | if (!existsSync(indexPath)) {
27 | throw new Error(`Index not found. Run: npm run build`);
28 | }
29 |
30 | const raw = await fs.readFile(indexPath, 'utf8');
31 | return JSON.parse(raw);
32 | }
33 |
34 | async function getDocContent(libraryId: string, relFile: string): Promise<string> {
35 | const sourcePath = getSourcePath(libraryId);
36 | if (!sourcePath) return '# No content';
37 |
38 | const fullPath = join(PROJECT_ROOT, 'sources', sourcePath, relFile);
39 | if (!existsSync(fullPath)) return '# No content';
40 |
41 | return await fs.readFile(fullPath, 'utf8');
42 | }
43 |
44 | async function testUrl(url: string): Promise<{ok: boolean, status: number, time: number}> {
45 | const start = Date.now();
46 | try {
47 | const response = await fetch(url, { method: 'HEAD' });
48 | return { ok: response.ok, status: response.status, time: Date.now() - start };
49 | } catch {
50 | return { ok: false, status: 0, time: Date.now() - start };
51 | }
52 | }
53 |
54 | async function quickTest(sourceFilter?: string, count: number = 3) {
55 | console.log(`${c.bold}${c.blue}🔗 Quick URL Test${c.reset}\n`);
56 |
57 | const index = await loadIndex();
58 | const sources = Object.values(index).filter((lib: any) => {
59 | if (sourceFilter) {
60 | return lib.id.includes(sourceFilter) || lib.name.toLowerCase().includes(sourceFilter.toLowerCase());
61 | }
62 | return getDocUrlConfig(lib.id) !== null; // Only test sources with URL configs
63 | });
64 |
65 | if (sources.length === 0) {
66 | console.log(`${c.red}No sources found matching "${sourceFilter}"${c.reset}`);
67 | return;
68 | }
69 |
70 | console.log(`Testing ${count} URLs from ${sources.length} source(s)...\n`);
71 |
72 | for (const lib of sources) {
73 | console.log(`${c.bold}📚 ${lib.name}${c.reset}`);
74 |
75 | const randomDocs = lib.docs
76 | .sort(() => 0.5 - Math.random())
77 | .slice(0, count);
78 |
79 | for (const doc of randomDocs) {
80 | try {
81 | const config = getDocUrlConfig(lib.id);
82 | if (!config) {
83 | console.log(`${c.yellow} ⚠️ No URL config for ${lib.id}${c.reset}`);
84 | continue;
85 | }
86 |
87 | const content = await getDocContent(lib.id, doc.relFile);
88 | const url = generateDocumentationUrl(lib.id, doc.relFile, content, config);
89 |
90 | if (!url) {
91 | console.log(`${c.yellow} ❌ Could not generate URL for: ${doc.title}${c.reset}`);
92 | continue;
93 | }
94 |
95 | const result = await testUrl(url);
96 | const statusColor = result.ok ? c.green : c.red;
97 | const icon = result.ok ? '✅' : '❌';
98 |
99 | console.log(` ${icon} ${statusColor}[${result.status}]${c.reset} ${doc.title.substring(0, 50)}${doc.title.length > 50 ? '...' : ''}`);
100 | console.log(` ${c.blue}${url}${c.reset}`);
101 | console.log(` ${result.time}ms\n`);
102 |
103 | } catch (error) {
104 | console.log(` ${c.red}❌ Error testing ${doc.title}: ${error}${c.reset}\n`);
105 | }
106 | }
107 | }
108 | }
109 |
110 | // CLI usage
111 | const args = process.argv.slice(2);
112 | const sourceFilter = args[0];
113 | const count = parseInt(args[1]) || 3;
114 |
115 | console.log(`${c.bold}Usage:${c.reset} npx tsx test/quick-url-test.ts [source-filter] [count]`);
116 | console.log(`${c.bold}Examples:${c.reset}`);
117 | console.log(` npx tsx test/quick-url-test.ts cloud-sdk 2 # Test 2 URLs from Cloud SDK sources`);
118 | console.log(` npx tsx test/quick-url-test.ts cap 5 # Test 5 URLs from CAP`);
119 | console.log(` npx tsx test/quick-url-test.ts ui5 1 # Test 1 URL from UI5 sources`);
120 | console.log(` npx tsx test/quick-url-test.ts # Test 3 URLs from all sources\n`);
121 |
122 | quickTest(sourceFilter, count).catch(console.error);
123 |
124 |
```
--------------------------------------------------------------------------------
/test-community-search.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | // Test script for the new community search functionality
4 | // Tests both search and detailed post retrieval
5 |
6 | import { searchCommunityBestMatch, getCommunityPostByUrl } from './dist/src/lib/communityBestMatch.js';
7 |
8 | async function testCommunitySearch() {
9 | console.log('🔍 Testing SAP Community Search with HTML Scraping\n');
10 |
11 | const testQueries = [
12 | 'odata cache',
13 | // 'CAP authentication',
14 | // 'odata binding',
15 | // 'fiori elements'
16 | ];
17 |
18 | for (const query of testQueries) {
19 | console.log(`\n📝 Testing query: "${query}"`);
20 | console.log('=' .repeat(50));
21 |
22 | try {
23 | const results = await searchCommunityBestMatch(query, {
24 | includeBlogs: true,
25 | limit: 5,
26 | userAgent: 'SAP-Docs-MCP-Test/1.0'
27 | });
28 |
29 | if (results.length === 0) {
30 | console.log('❌ No results found');
31 | continue;
32 | }
33 |
34 | console.log(`✅ Found ${results.length} results:`);
35 |
36 | results.forEach((result, index) => {
37 | console.log(`\n${index + 1}. ${result.title}`);
38 | console.log(` URL: ${result.url}`);
39 | console.log(` Author: ${result.author || 'Unknown'}`);
40 | console.log(` Published: ${result.published || 'Unknown'}`);
41 | console.log(` Likes: ${result.likes || 0}`);
42 | console.log(` Snippet: ${result.snippet ? result.snippet.substring(0, 100) + '...' : 'No snippet'}`);
43 | console.log(` Tags: ${result.tags?.join(', ') || 'None'}`);
44 | console.log(` Post ID: ${result.postId || 'Not extracted'}`);
45 |
46 | // Verify post ID extraction
47 | if (result.postId) {
48 | console.log(` ✅ Post ID extracted: ${result.postId}`);
49 | } else {
50 | console.log(` ⚠️ Post ID not extracted from URL: ${result.url}`);
51 | }
52 | });
53 |
54 | // Test detailed post retrieval for the first result
55 | if (results.length > 0) {
56 | console.log(`\n🔎 Testing detailed post retrieval for: "${results[0].title}"`);
57 | console.log('-'.repeat(50));
58 |
59 | try {
60 | const postContent = await getCommunityPostByUrl(results[0].url, 'SAP-Docs-MCP-Test/1.0');
61 |
62 | if (postContent) {
63 | console.log('✅ Successfully retrieved full post content:');
64 | console.log(postContent.substring(0, 500) + '...\n');
65 | } else {
66 | console.log('❌ Failed to retrieve full post content');
67 | }
68 | } catch (error) {
69 | console.log(`❌ Error retrieving post content: ${error.message}`);
70 | }
71 | }
72 |
73 | } catch (error) {
74 | console.log(`❌ Error searching for "${query}": ${error.message}`);
75 | }
76 |
77 | // Add delay between requests to be respectful
78 | await new Promise(resolve => setTimeout(resolve, 2000));
79 | }
80 | }
81 |
82 | async function testSpecificPost() {
83 | console.log('\n🎯 Testing specific post retrieval');
84 | console.log('=' .repeat(50));
85 |
86 | // Test with the known SAP Community URL from your example
87 | const testUrl = 'https://community.sap.com/t5/technology-blog-posts-by-sap/fiori-cache-maintenance/ba-p/13961398';
88 |
89 | try {
90 | console.log(`Testing URL: ${testUrl}`);
91 | console.log(`Expected Post ID: 13961398`);
92 |
93 | const content = await getCommunityPostByUrl(testUrl, 'SAP-Docs-MCP-Test/1.0');
94 |
95 | if (content) {
96 | console.log('✅ Successfully retrieved content:');
97 | console.log(content.substring(0, 800) + '...');
98 |
99 | // Verify the content contains expected elements
100 | if (content.includes('FIORI Cache Maintenance')) {
101 | console.log('✅ Title extraction successful');
102 | }
103 | if (content.includes('MarkNed')) {
104 | console.log('✅ Author extraction successful');
105 | }
106 | if (content.includes('SMICM')) {
107 | console.log('✅ Content extraction successful');
108 | }
109 | } else {
110 | console.log('❌ No content retrieved');
111 | }
112 | } catch (error) {
113 | console.log(`❌ Error: ${error.message}`);
114 | }
115 | }
116 |
117 | async function main() {
118 | console.log('🚀 Starting SAP Community Search Tests');
119 | console.log('=====================================');
120 |
121 | try {
122 | await testCommunitySearch();
123 | await testSpecificPost();
124 |
125 | console.log('\n✅ All tests completed!');
126 | } catch (error) {
127 | console.error('❌ Test suite failed:', error);
128 | process.exit(1);
129 | }
130 | }
131 |
132 | // Handle graceful shutdown
133 | process.on('SIGINT', () => {
134 | console.log('\n👋 Test interrupted by user');
135 | process.exit(0);
136 | });
137 |
138 | // Run the tests
139 | main().catch(error => {
140 | console.error('💥 Unexpected error:', error);
141 | process.exit(1);
142 | });
```
--------------------------------------------------------------------------------
/test/tools/search-url-verification.js:
--------------------------------------------------------------------------------
```javascript
1 | // MCP Search URL Verification Test Cases
2 | // Verifies that search results from the MCP server include proper documentation URLs
3 |
4 | import { readFileSync } from 'node:fs';
5 | import { dirname, join } from 'node:path';
6 | import { fileURLToPath } from 'node:url';
7 |
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = dirname(__filename);
10 |
11 | function loadAllowedUrlPrefixes() {
12 | const metadataPath = join(__dirname, '..', '..', 'src', 'metadata.json');
13 | const raw = readFileSync(metadataPath, 'utf8');
14 | const metadata = JSON.parse(raw);
15 |
16 | const prefixes = new Set();
17 | for (const source of metadata?.sources || []) {
18 | if (typeof source.baseUrl === 'string' && source.baseUrl.trim().length > 0) {
19 | const normalized = source.baseUrl.replace(/\/$/, '');
20 | prefixes.add(normalized);
21 | }
22 | }
23 |
24 | return prefixes;
25 | }
26 |
27 | const allowedPrefixes = loadAllowedUrlPrefixes();
28 | const allowedPrefixList = Array.from(allowedPrefixes);
29 |
30 | function extractUrls(text) {
31 | const regex = /🔗\s+(https?:\/\/[^\s]+)/g;
32 | const urls = [];
33 | let match;
34 | while ((match = regex.exec(text)) !== null) {
35 | urls.push(match[1]);
36 | }
37 | return urls;
38 | }
39 |
40 | function isAllowedDocumentationUrl(url) {
41 | try {
42 | const normalized = url.replace(/\/$/, '');
43 | for (const prefix of allowedPrefixes) {
44 | if (normalized === prefix || normalized.startsWith(`${prefix}/`) || normalized.startsWith(`${prefix}#`)) {
45 | return true;
46 | }
47 | }
48 | return false;
49 | } catch (_) {
50 | return false;
51 | }
52 | }
53 |
54 | export default [
55 | {
56 | name: 'CAP CDS - Should include documentation URL',
57 | tool: 'search',
58 | query: 'cds query language',
59 | skipIfNoResults: true,
60 | expectIncludes: ['/cap/'],
61 | expectContains: ['🔗'], // Should contain URL link emoji
62 | expectUrlPattern: 'https://cap.cloud.sap/docs'
63 | },
64 | {
65 | name: 'Cloud SDK JS - Should include documentation URL',
66 | tool: 'search',
67 | query: 'cloud sdk javascript remote debugging',
68 | skipIfNoResults: true,
69 | expectIncludes: ['/cloud-sdk-js/'],
70 | expectContains: ['🔗'],
71 | expectUrlPattern: 'https://sap.github.io/cloud-sdk/docs/js'
72 | },
73 | {
74 | name: 'SAPUI5 - Should include documentation URL',
75 | tool: 'search',
76 | query: 'sapui5 button control',
77 | skipIfNoResults: true,
78 | expectIncludes: ['/sapui5/'],
79 | expectContains: ['🔗'],
80 | expectUrlPattern: 'https://ui5.sap.com'
81 | },
82 | {
83 | name: 'wdi5 - Should include documentation URL',
84 | tool: 'search',
85 | query: 'wdi5 locators testing',
86 | skipIfNoResults: true,
87 | expectIncludes: ['/wdi5/'],
88 | expectContains: ['🔗'],
89 | expectUrlPattern: 'https://ui5-community.github.io/wdi5'
90 | },
91 | {
92 | name: 'UI5 Tooling - Should include documentation URL',
93 | tool: 'search',
94 | query: 'ui5 tooling build',
95 | skipIfNoResults: true,
96 | expectIncludes: ['/ui5-tooling/'],
97 | expectContains: ['🔗'],
98 | expectUrlPattern: 'https://sap.github.io/ui5-tooling'
99 | },
100 | {
101 | name: 'Search results should have consistent format with excerpts',
102 | tool: 'search',
103 | query: 'button',
104 | skipIfNoResults: true,
105 | expectIncludes: ['Score:', '🔗', 'Use in fetch'],
106 | expectPattern: /⭐️\s+\*\*[^*]+\*\*\s+\(Score:\s+[\d.]+\)/
107 | },
108 | {
109 | name: 'All returned documentation URLs should be HTTPS and match known sources',
110 | async validate({ docsSearch }) {
111 | const response = await docsSearch('sap');
112 | if (/No results found/.test(response)) {
113 | return {
114 | skipped: true,
115 | message: 'no documentation results available to validate'
116 | };
117 | }
118 | const urls = extractUrls(response);
119 |
120 | if (!urls.length) {
121 | return {
122 | passed: false,
123 | message: 'No documentation URLs were found in the response.'
124 | };
125 | }
126 |
127 | const invalidUrls = urls.filter(url => !/^https:\/\//.test(url) || !isAllowedDocumentationUrl(url));
128 |
129 | if (invalidUrls.length) {
130 | return {
131 | passed: false,
132 | message: `Found URLs that are not allowed or not HTTPS: ${invalidUrls.join(', ')}`
133 | };
134 | }
135 |
136 | return { passed: true };
137 | }
138 | },
139 | {
140 | name: 'Metadata should expose base URLs for critical SAP documentation sources',
141 | async validate() {
142 | const requiredPrefixes = [
143 | 'https://cap.cloud.sap',
144 | 'https://sap.github.io/cloud-sdk',
145 | 'https://ui5.sap.com',
146 | 'https://ui5-community.github.io/wdi5'
147 | ];
148 |
149 | const missing = requiredPrefixes.filter(prefix => {
150 | return !allowedPrefixList.some(allowed => allowed === prefix || allowed.startsWith(prefix));
151 | });
152 |
153 | if (missing.length) {
154 | return {
155 | passed: false,
156 | message: `Missing required base URL prefixes in metadata: ${missing.join(', ')}`
157 | };
158 | }
159 |
160 | return { passed: true };
161 | }
162 | }
163 | ];
164 |
```
--------------------------------------------------------------------------------
/REMOTE_SETUP.md:
--------------------------------------------------------------------------------
```markdown
1 | # Remote Server Setup Guide
2 |
3 | This guide explains how to connect to the hosted SAP Documentation MCP Server for instant access to SAP documentation and community content without local setup.
4 |
5 | ## 🚀 Quick Setup (2 minutes)
6 |
7 | ### Step 1: Locate Your MCP Configuration File
8 |
9 | The MCP configuration file location depends on your operating system:
10 |
11 | | OS | Location |
12 | |---|---|
13 | | **macOS** | `~/.cursor/mcp.json` |
14 | | **Linux** | `~/.cursor/mcp.json` |
15 | | **Windows** | `%APPDATA%\Cursor\mcp.json` |
16 |
17 | ### Step 2: Create or Edit the Configuration File
18 |
19 | If the file doesn't exist, create it. If it exists, add the new server to the existing configuration.
20 |
21 | #### New Configuration File
22 | ```json
23 | {
24 | "mcpServers": {
25 | "sap-docs-remote": {
26 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
27 | }
28 | }
29 | }
30 | ```
31 |
32 | #### Adding to Existing Configuration
33 | If you already have other MCP servers configured, add the new server:
34 |
35 | ```json
36 | {
37 | "mcpServers": {
38 | "existing-server": {
39 | "command": "some-command"
40 | },
41 | "sap-docs-remote": {
42 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
43 | }
44 | }
45 | }
46 | ```
47 |
48 | ### Step 3: Restart Cursor
49 |
50 | Close and reopen Cursor to load the new MCP server configuration.
51 |
52 | ### Step 4: Test the Connection
53 |
54 | Ask Cursor a SAP-related question to verify the connection:
55 |
56 | - "How do I implement authentication in SAPUI5?"
57 | - "Show me wdi5 testing examples"
58 | - "What are CAP service best practices?"
59 |
60 | ## 🎯 What You Get
61 |
62 | ### Comprehensive Documentation Access
63 | - **SAPUI5 Documentation**: 1,485+ files with complete developer guides
64 | - **CAP Documentation**: 195+ files covering Cloud Application Programming
65 | - **OpenUI5 API Documentation**: 500+ control APIs with detailed JSDoc
66 | - **OpenUI5 Sample Code**: 2,000+ working examples
67 | - **wdi5 Documentation**: End-to-end test framework docs
68 |
69 | ### Real-Time Community Content
70 | - **SAP Community Posts**: Latest blog posts and solutions
71 | - **High-Quality Content**: Engagement info (kudos) included when available; results follow SAP Community's Best Match ranking
72 | - **Code Examples**: Real-world implementations from developers
73 | - **Best Practices**: Community-tested approaches
74 |
75 | ## 🔧 Troubleshooting
76 |
77 | ### Server Not Responding
78 | 1. Check your internet connection
79 | 2. Verify the URL is correct: `https://mcp-sap-docs.marianzeis.de/mcp`
80 | 3. Restart Cursor
81 | 4. Check Cursor's MCP server status in settings
82 |
83 | ### Configuration Not Loading
84 | 1. Verify the JSON syntax is correct (use a JSON validator)
85 | 2. Ensure the file path is correct for your OS
86 | 3. Check file permissions (should be readable by your user)
87 | 4. Restart Cursor after any configuration changes
88 |
89 | ### No SAP Documentation in Responses
90 | 1. Try asking more specific SAP-related questions
91 | 2. Include keywords like "SAPUI5", "CAP", "wdi5", or "SAP Community"
92 | 3. Check if other MCP servers might be taking precedence
93 |
94 | ## 💡 Usage Tips
95 |
96 | ### Effective Prompts
97 | Instead of generic questions, use specific SAP terminology:
98 |
99 | ✅ **Good:**
100 | - "How do I implement SAPUI5 data binding with OData models?"
101 | - "Show me wdi5 test examples for table interactions"
102 | - "What are CAP service annotations for authorization?"
103 |
104 | ❌ **Less Effective:**
105 | - "How do I bind data?"
106 | - "Show me test examples"
107 | - "What are service annotations?"
108 |
109 | ### Combining Local and Remote
110 | You can use both local and remote MCP servers simultaneously:
111 |
112 | ```json
113 | {
114 | "mcpServers": {
115 | "sap-docs-local": {
116 | "command": "node",
117 | "args": ["/path/to/local/server.js"]
118 | },
119 | "sap-docs-remote": {
120 | "url": "https://mcp-sap-docs.marianzeis.de/mcp"
121 | }
122 | }
123 | }
124 | ```
125 |
126 | ## 🌐 Server Information
127 |
128 | - **URL**: https://mcp-sap-docs.marianzeis.de
129 | - **Health Check**: https://mcp-sap-docs.marianzeis.de/status
130 | - **Protocol**: MCP Streamable HTTP
131 | - **Uptime**: Monitored 24/7 with automatic deployment
132 | - **Updates**: Automatically synced with latest documentation
133 | - **Environment**: Uses PM2 for process management (no Docker)
134 |
135 | ## 🖥️ Local Development
136 |
137 | ### VS Code Performance Optimization
138 | The large documentation submodules are excluded from VS Code operations to prevent crashes:
139 |
140 | - **Search Excluded**: `sources/`, `node_modules/`, `dist/`, `data/`
141 | - **File Explorer Hidden**: Large submodule folders are hidden
142 | - **Git Operations**: Submodules are excluded from local git tracking
143 |
144 | ### Local Setup
145 | For local development without the large submodules:
146 |
147 | ```bash
148 | # Quick setup (excludes large submodules)
149 | npm run setup
150 |
151 | # Or manually initialize submodules
152 | npm run setup:submodules
153 | ```
154 |
155 | ## 📞 Support
156 |
157 | If you encounter issues:
158 |
159 | 1. Check the [repository issues](https://github.com/marianfoo/mcp-sap-docs/issues)
160 | 2. Verify server status at the health check URL
161 | 3. Create a new issue with your configuration and error details
162 |
163 | ---
164 |
165 | *The remote server provides the same comprehensive SAP documentation access as the local installation but with zero setup complexity and automatic updates.*
```
--------------------------------------------------------------------------------
/test/url-status.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | /**
3 | * URL Status - Shows which sources have URL generation configured
4 | */
5 |
6 | import { fileURLToPath } from 'url';
7 | import { dirname, join } from 'path';
8 | import fs from 'fs/promises';
9 | import { existsSync } from 'fs';
10 | import { getDocUrlConfig, getSourcePath, getMetadata } from '../src/lib/metadata.js';
11 |
12 | const __filename = fileURLToPath(import.meta.url);
13 | const __dirname = dirname(__filename);
14 | const PROJECT_ROOT = join(__dirname, '..');
15 | const DATA_DIR = join(PROJECT_ROOT, 'dist', 'data');
16 |
17 | // Colors
18 | const c = {
19 | green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
20 | blue: '\x1b[34m', bold: '\x1b[1m', reset: '\x1b[0m', dim: '\x1b[2m'
21 | };
22 |
23 | async function loadIndex() {
24 | const indexPath = join(DATA_DIR, 'index.json');
25 | if (!existsSync(indexPath)) {
26 | throw new Error(`Index not found. Run: npm run build`);
27 | }
28 |
29 | const raw = await fs.readFile(indexPath, 'utf8');
30 | return JSON.parse(raw);
31 | }
32 |
33 | async function showUrlStatus() {
34 | console.log(`${c.bold}${c.blue}🔗 URL Generation Status${c.reset}\n`);
35 |
36 | try {
37 | const index = await loadIndex();
38 | const metadata = getMetadata();
39 | const sources = Object.values(index);
40 |
41 | console.log(`Found ${sources.length} documentation sources:\n`);
42 |
43 | // Group sources by status
44 | const withUrls: any[] = [];
45 | const withoutUrls: any[] = [];
46 |
47 | sources.forEach((lib: any) => {
48 | const config = getDocUrlConfig(lib.id);
49 | if (config) {
50 | withUrls.push({ lib, config });
51 | } else {
52 | withoutUrls.push(lib);
53 | }
54 | });
55 |
56 | // Show sources with URL generation
57 | if (withUrls.length > 0) {
58 | console.log(`${c.bold}${c.green}✅ Sources with URL generation (${withUrls.length}):${c.reset}`);
59 | withUrls.forEach(({ lib, config }) => {
60 | console.log(` ${c.green}✅${c.reset} ${c.bold}${lib.name}${c.reset} (${lib.id})`);
61 | console.log(` ${c.dim}📄 ${lib.docs.length} documents${c.reset}`);
62 | console.log(` ${c.dim}🌐 ${config.baseUrl}${c.reset}`);
63 | console.log(` ${c.dim}📋 Pattern: ${config.pathPattern}${c.reset}`);
64 | console.log(` ${c.dim}⚙️ Anchor style: ${config.anchorStyle}${c.reset}`);
65 | console.log();
66 | });
67 | }
68 |
69 | // Show sources without URL generation
70 | if (withoutUrls.length > 0) {
71 | console.log(`${c.bold}${c.red}❌ Sources without URL generation (${withoutUrls.length}):${c.reset}`);
72 | withoutUrls.forEach((lib: any) => {
73 | console.log(` ${c.red}❌${c.reset} ${c.bold}${lib.name}${c.reset} (${lib.id})`);
74 | console.log(` ${c.dim}📄 ${lib.docs.length} documents${c.reset}`);
75 | console.log(` ${c.dim}💡 Needs baseUrl, pathPattern, and anchorStyle in metadata.json${c.reset}`);
76 | console.log();
77 | });
78 | }
79 |
80 | // Show URL generation handlers status
81 | console.log(`${c.bold}${c.blue}🔧 URL Generation Handlers:${c.reset}`);
82 |
83 | const handlers = [
84 | { pattern: '/cloud-sdk-*', description: 'Cloud SDK (JS/Java/AI variants)', status: 'implemented' },
85 | { pattern: '/sapui5', description: 'SAPUI5 topic-based URLs', status: 'implemented' },
86 | { pattern: '/openui5-*', description: 'OpenUI5 API & samples', status: 'implemented' },
87 | { pattern: '/cap', description: 'CAP docsify-style URLs', status: 'implemented' },
88 | { pattern: '/wdi5', description: 'wdi5 testing framework', status: 'implemented' },
89 | { pattern: '/ui5-tooling', description: 'UI5 Tooling (fallback)', status: 'fallback' },
90 | { pattern: '/ui5-webcomponents', description: 'UI5 Web Components (fallback)', status: 'fallback' },
91 | { pattern: '/cloud-mta-build-tool', description: 'MTA Build Tool (fallback)', status: 'fallback' }
92 | ];
93 |
94 | handlers.forEach(handler => {
95 | const statusIcon = handler.status === 'implemented' ? `${c.green}✅` : `${c.yellow}⚠️ `;
96 | const statusText = handler.status === 'implemented' ? 'Specialized handler' : 'Generic fallback';
97 |
98 | console.log(` ${statusIcon}${c.reset} ${c.bold}${handler.pattern}${c.reset}`);
99 | console.log(` ${c.dim}${handler.description}${c.reset}`);
100 | console.log(` ${c.dim}${statusText}${c.reset}`);
101 | console.log();
102 | });
103 |
104 | // Summary
105 | console.log(`${c.bold}${c.blue}📊 Summary:${c.reset}`);
106 | console.log(` Sources with URL generation: ${c.green}${withUrls.length}${c.reset}/${sources.length}`);
107 | console.log(` Specialized handlers: ${c.green}5${c.reset} (Cloud SDK, UI5, CAP, wdi5)`);
108 | console.log(` Fallback handlers: ${c.yellow}3${c.reset} (Tooling, Web Components, MTA)`);
109 |
110 | const coverage = Math.round((withUrls.length / sources.length) * 100);
111 | const coverageColor = coverage > 80 ? c.green : coverage > 60 ? c.yellow : c.red;
112 | console.log(` URL generation coverage: ${coverageColor}${coverage}%${c.reset}`);
113 |
114 | } catch (error) {
115 | console.error(`${c.red}Error: ${error}${c.reset}`);
116 | }
117 | }
118 |
119 | showUrlStatus();
120 |
121 |
```
--------------------------------------------------------------------------------
/test-search-interactive.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env tsx
2 | // Interactive search test with multiple keywords and analysis
3 | import { searchLibraries } from './src/lib/localDocs.ts';
4 | import { createInterface } from 'readline';
5 |
6 | const rl = createInterface({
7 | input: process.stdin,
8 | output: process.stdout
9 | });
10 |
11 | // Predefined test cases with expected contexts
12 | const testCases = [
13 | { query: 'wizard', expectedContext: 'UI5', description: 'UI5 Wizard control' },
14 | { query: 'cds entity', expectedContext: 'CAP', description: 'CAP entity definition' },
15 | { query: 'wdi5 testing', expectedContext: 'wdi5', description: 'wdi5 testing framework' },
16 | { query: 'service', expectedContext: 'CAP', description: 'Generic service (should prioritize CAP)' },
17 | { query: 'annotation', expectedContext: 'MIXED', description: 'Annotations (CAP/UI5)' },
18 | { query: 'sap.m.Button', expectedContext: 'UI5', description: 'Specific UI5 control' },
19 | { query: 'browser automation', expectedContext: 'wdi5', description: 'Browser testing' },
20 | { query: 'fiori elements', expectedContext: 'UI5', description: 'Fiori Elements' }
21 | ];
22 |
23 | async function runSingleTest(query: string, expectedContext?: string) {
24 | console.log(`\n${'='.repeat(60)}`);
25 | console.log(`🔍 Testing: "${query}"${expectedContext ? ` (Expected: ${expectedContext})` : ''}`);
26 | console.log(`${'='.repeat(60)}`);
27 |
28 | try {
29 | const startTime = Date.now();
30 | const result = await searchLibraries(query);
31 | const endTime = Date.now();
32 |
33 | if (result.results.length > 0) {
34 | const description = result.results[0].description;
35 |
36 | // Extract detected context
37 | const contextMatch = description.match(/\*\*(\w+) Context\*\*/);
38 | const detectedContext = contextMatch ? contextMatch[1] : 'Unknown';
39 |
40 | // Show performance and context
41 | console.log(`⏱️ Search time: ${endTime - startTime}ms`);
42 | console.log(`🎯 Detected context: ${detectedContext}`);
43 | if (expectedContext) {
44 | const isCorrect = detectedContext === expectedContext || expectedContext === 'MIXED';
45 | console.log(`✅ Context match: ${isCorrect ? 'YES' : 'NO'} ${isCorrect ? '✅' : '❌'}`);
46 | }
47 |
48 | // Extract first few results for summary
49 | const lines = description.split('\n');
50 | const foundLine = lines.find(line => line.includes('Found'));
51 | if (foundLine) {
52 | console.log(`📊 ${foundLine}`);
53 | }
54 |
55 | // Show top result
56 | const topResultLine = lines.find(line => line.includes('⭐️'));
57 | if (topResultLine) {
58 | console.log(`🏆 Top result: ${topResultLine.substring(0, 80)}...`);
59 | }
60 |
61 | // Show library sections found
62 | const sections = [];
63 | if (description.includes('🏗️ **CAP Documentation:**')) sections.push('CAP');
64 | if (description.includes('🧪 **wdi5 Documentation:**')) sections.push('wdi5');
65 | if (description.includes('📖 **SAPUI5 Guides:**')) sections.push('SAPUI5');
66 | if (description.includes('🔹 **UI5 API Documentation:**')) sections.push('UI5-API');
67 | if (description.includes('🔸 **UI5 Samples:**')) sections.push('UI5-Samples');
68 |
69 | console.log(`📚 Libraries found: ${sections.join(', ') || 'None'}`);
70 |
71 | } else {
72 | console.log('❌ No results found');
73 | if (result.error) {
74 | console.log(`📝 Error: ${result.error}`);
75 | }
76 | }
77 |
78 | } catch (error) {
79 | console.error('❌ Search failed:', error);
80 | }
81 | }
82 |
83 | async function runAllTests() {
84 | console.log('🧪 Running all predefined test cases...\n');
85 |
86 | for (const testCase of testCases) {
87 | await runSingleTest(testCase.query, testCase.expectedContext);
88 | }
89 |
90 | console.log('\n🎉 All tests completed!');
91 | }
92 |
93 | async function interactiveMode() {
94 | console.log(`
95 | 🎯 SAP Docs Interactive Search Test
96 |
97 | Commands:
98 | - Enter any search term to test
99 | - 'all' - Run all predefined tests
100 | - 'list' - Show predefined test cases
101 | - 'quit' or 'exit' - Exit
102 | `);
103 |
104 | const question = () => {
105 | rl.question('\n🔍 Enter search term (or command): ', async (input) => {
106 | const trimmed = input.trim();
107 |
108 | if (trimmed === 'quit' || trimmed === 'exit') {
109 | console.log('👋 Goodbye!');
110 | rl.close();
111 | return;
112 | }
113 |
114 | if (trimmed === 'all') {
115 | await runAllTests();
116 | question();
117 | return;
118 | }
119 |
120 | if (trimmed === 'list') {
121 | console.log('\n📋 Predefined test cases:');
122 | testCases.forEach((tc, i) => {
123 | console.log(` ${i + 1}. "${tc.query}" (${tc.expectedContext}) - ${tc.description}`);
124 | });
125 | question();
126 | return;
127 | }
128 |
129 | if (trimmed) {
130 | await runSingleTest(trimmed);
131 | }
132 |
133 | question();
134 | });
135 | };
136 |
137 | question();
138 | }
139 |
140 | // Main execution
141 | const args = process.argv.slice(2);
142 |
143 | if (args.length === 0) {
144 | interactiveMode();
145 | } else if (args[0] === 'all') {
146 | runAllTests().then(() => process.exit(0));
147 | } else {
148 | const query = args.join(' ');
149 | runSingleTest(query).then(() => process.exit(0));
150 | }
```
--------------------------------------------------------------------------------
/src/lib/url-generation/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Main entry point for URL generation across all documentation sources
3 | * Dispatches to source-specific generators based on library ID
4 | */
5 |
6 | import { DocUrlConfig } from '../metadata.js';
7 | import { CloudSdkUrlGenerator } from './cloud-sdk.js';
8 | import { SapUi5UrlGenerator } from './sapui5.js';
9 | import { CapUrlGenerator } from './cap.js';
10 | import { Wdi5UrlGenerator } from './wdi5.js';
11 | import { DsagUrlGenerator } from './dsag.js';
12 | import { AbapUrlGenerator } from './abap.js';
13 | import { GenericUrlGenerator } from './GenericUrlGenerator.js';
14 | import { BaseUrlGenerator } from './BaseUrlGenerator.js';
15 |
16 | export interface UrlGenerationOptions {
17 | libraryId: string;
18 | relFile: string;
19 | content: string;
20 | config: DocUrlConfig;
21 | }
22 |
23 | /**
24 | * URL Generator Registry
25 | * Maps library IDs to their corresponding URL generator classes
26 | */
27 | const URL_GENERATORS: Record<string, new (libraryId: string, config: DocUrlConfig) => BaseUrlGenerator> = {
28 | // Cloud SDK variants
29 | '/cloud-sdk-js': CloudSdkUrlGenerator,
30 | '/cloud-sdk-java': CloudSdkUrlGenerator,
31 | '/cloud-sdk-ai-js': CloudSdkUrlGenerator,
32 | '/cloud-sdk-ai-java': CloudSdkUrlGenerator,
33 |
34 | // UI5 variants
35 | '/sapui5': SapUi5UrlGenerator,
36 | '/openui5-api': SapUi5UrlGenerator,
37 | '/openui5-samples': SapUi5UrlGenerator,
38 |
39 | // CAP documentation
40 | '/cap': CapUrlGenerator,
41 |
42 | // wdi5 testing framework
43 | '/wdi5': Wdi5UrlGenerator,
44 |
45 | // DSAG ABAP Leitfaden with custom GitHub Pages URL pattern
46 | '/dsag-abap-leitfaden': DsagUrlGenerator,
47 |
48 | // ABAP Keyword Documentation with SAP help.sap.com URLs (all versions)
49 | '/abap-docs-758': AbapUrlGenerator,
50 | '/abap-docs-757': AbapUrlGenerator,
51 | '/abap-docs-756': AbapUrlGenerator,
52 | '/abap-docs-755': AbapUrlGenerator,
53 | '/abap-docs-754': AbapUrlGenerator,
54 | '/abap-docs-753': AbapUrlGenerator,
55 | '/abap-docs-752': AbapUrlGenerator,
56 | '/abap-docs-latest': AbapUrlGenerator,
57 |
58 | // Generic sources
59 | '/ui5-tooling': GenericUrlGenerator,
60 | '/cloud-mta-build-tool': GenericUrlGenerator,
61 | '/ui5-webcomponents': GenericUrlGenerator,
62 | '/ui5-typescript': GenericUrlGenerator,
63 | '/ui5-cc-spreadsheetimporter': GenericUrlGenerator,
64 | '/abap-cheat-sheets': GenericUrlGenerator,
65 | '/sap-styleguides': GenericUrlGenerator,
66 | '/abap-fiori-showcase': GenericUrlGenerator,
67 | '/cap-fiori-showcase': GenericUrlGenerator,
68 | };
69 |
70 | /**
71 | * Create URL generator for a given library ID
72 | */
73 | function createUrlGenerator(libraryId: string, config: DocUrlConfig): BaseUrlGenerator {
74 | const GeneratorClass = URL_GENERATORS[libraryId];
75 |
76 | if (GeneratorClass) {
77 | return new GeneratorClass(libraryId, config);
78 | }
79 |
80 | // Fallback to generic generator for unknown sources
81 | console.log(`Using generic URL generator for unknown library: ${libraryId}`);
82 | return new GenericUrlGenerator(libraryId, config);
83 | }
84 |
85 | /**
86 | * Main URL generation function
87 | * Uses class-based generators for cleaner, more maintainable code
88 | *
89 | * @param libraryId - The library/source identifier (e.g., '/cloud-sdk-js')
90 | * @param relFile - Relative file path within the source
91 | * @param content - File content for extracting metadata
92 | * @param config - URL configuration for this source
93 | * @returns Generated URL or null if generation fails
94 | */
95 | export function generateDocumentationUrl(
96 | libraryId: string,
97 | relFile: string,
98 | content: string,
99 | config: DocUrlConfig
100 | ): string | null {
101 | if (!config) {
102 | console.warn(`No URL config available for library: ${libraryId}`);
103 | return null;
104 | }
105 |
106 | try {
107 | const generator = createUrlGenerator(libraryId, config);
108 | const url = generator.generateUrl({
109 | libraryId,
110 | relFile,
111 | content,
112 | config
113 | });
114 |
115 | return url;
116 | } catch (error) {
117 | console.warn(`Error generating URL for ${libraryId}:`, error);
118 | return null;
119 | }
120 | }
121 |
122 | // Re-export utilities and generator classes for external use
123 | export { parseFrontmatter, detectContentSection, extractSectionFromPath, buildUrl, extractLibraryIdFromPath, extractRelativeFileFromPath, formatSearchResult } from './utils.js';
124 | export { BaseUrlGenerator } from './BaseUrlGenerator.js';
125 | export type { UrlGenerationContext } from './BaseUrlGenerator.js';
126 |
127 | // Re-export generator classes
128 | export { CloudSdkUrlGenerator } from './cloud-sdk.js';
129 | export { SapUi5UrlGenerator } from './sapui5.js';
130 | export { CapUrlGenerator } from './cap.js';
131 | export { Wdi5UrlGenerator } from './wdi5.js';
132 | export { DsagUrlGenerator } from './dsag.js';
133 | export { GenericUrlGenerator } from './GenericUrlGenerator.js';
134 |
135 | // Re-export convenience functions for backward compatibility
136 | export { generateCloudSdkUrl, generateCloudSdkAiUrl, generateCloudSdkUrlForLibrary } from './cloud-sdk.js';
137 | export { generateSapUi5Url, generateOpenUi5ApiUrl, generateOpenUi5SampleUrl, generateUi5UrlForLibrary } from './sapui5.js';
138 | export { generateCapUrl, generateCapCdsUrl, generateCapTutorialUrl } from './cap.js';
139 | export { generateWdi5Url, generateWdi5ConfigUrl, generateWdi5SelectorUrl } from './wdi5.js';
140 | export { generateDsagUrl } from './dsag.js';
141 | export { generateAbapUrl } from './abap.js';
142 |
```
--------------------------------------------------------------------------------
/docs/TEST-SEARCH.md:
--------------------------------------------------------------------------------
```markdown
1 | # 🔍 SAP Docs Search Testing Guide
2 |
3 | Test the enhanced context-aware search functionality using these testing tools.
4 |
5 | ## 📁 Test Files Available
6 |
7 | ### 1. **Simple Search Test** (`test-search.ts`)
8 | Quick command-line search testing with any keyword.
9 |
10 | ```bash
11 | # Test with default keyword
12 | npx tsx test-search.ts
13 |
14 | # Test specific keywords
15 | npx tsx test-search.ts wizard
16 | npx tsx test-search.ts "cds entity"
17 | npx tsx test-search.ts "wdi5 testing"
18 | npx tsx test-search.ts annotation
19 | ```
20 |
21 | **Features:**
22 | - ⏱️ Performance timing
23 | - 📊 Result summary
24 | - 🎯 Context detection display
25 | - 📖 Top result preview
26 |
27 | ### 2. **Interactive Search Test** (`test-search-interactive.ts`)
28 | Advanced testing with multiple modes and analysis.
29 |
30 | ```bash
31 | # Interactive mode
32 | npx tsx test-search-interactive.ts
33 |
34 | # Run all predefined tests
35 | npx tsx test-search-interactive.ts all
36 |
37 | # Test specific query
38 | npx tsx test-search-interactive.ts "your search term"
39 | ```
40 |
41 | **Interactive Commands:**
42 | - `all` - Run all predefined test cases
43 | - `list` - Show predefined test cases
44 | - `quit` / `exit` - Exit interactive mode
45 | - Any keyword - Test search
46 |
47 | **Features:**
48 | - 🧪 Predefined test cases with expected contexts
49 | - ✅ Context validation
50 | - 📚 Library breakdown analysis
51 | - 🏆 Top result highlighting
52 | - ⏱️ Performance metrics
53 |
54 | ### 3. **HTTP API Tests** (`test-search.http`) ✅ **WORKING**
55 | Test the HTTP server endpoints (requires VS Code REST Client or similar).
56 |
57 | **First, start the HTTP server:**
58 | ```bash
59 | npm run start:http
60 | ```
61 |
62 | **Then use the `.http` file to test:**
63 | - Server status check
64 | - Various search queries
65 | - Context-specific tests
66 | - **Full search functionality** with context-aware results
67 |
68 | **Example response for "wizard":**
69 | ```json
70 | {
71 | "role": "assistant",
72 | "content": "Found 10 results for 'wizard' 🎨 **UI5 Context**:\n\n🔹 **UI5 API Documentation:**\n⭐️ **sap.f.SidePanel** (Score: 100)..."
73 | }
74 | ```
75 |
76 | ## 🎯 Test Categories
77 |
78 | ### **UI5 Context Tests**
79 | ```bash
80 | npx tsx test-search.ts wizard
81 | npx tsx test-search.ts "sap.m.Button"
82 | npx tsx test-search.ts "fiori elements"
83 | ```
84 | Expected: 🎨 **UI5 Context** with UI5 API/Samples first
85 |
86 | ### **CAP Context Tests**
87 | ```bash
88 | npx tsx test-search.ts "cds entity"
89 | npx tsx test-search.ts service
90 | npx tsx test-search.ts aspect
91 | ```
92 | Expected: 🏗️ **CAP Context** with CAP Documentation first
93 |
94 | ### **wdi5 Context Tests**
95 | ```bash
96 | npx tsx test-search.ts "wdi5 testing"
97 | npx tsx test-search.ts "browser automation"
98 | npx tsx test-search.ts "e2e test"
99 | ```
100 | Expected: 🧪 **wdi5 Context** with wdi5 Documentation first
101 |
102 | ### **Mixed Context Tests**
103 | ```bash
104 | npx tsx test-search.ts annotation
105 | npx tsx test-search.ts authentication
106 | npx tsx test-search.ts routing
107 | ```
108 | Expected: Context varies based on strongest signal
109 |
110 | ## 📊 Understanding Results
111 |
112 | ### **Context Detection** 🎯
113 | - **🎨 UI5 Context**: UI5 controls, Fiori, frontend development
114 | - **🏗️ CAP Context**: CDS, entities, services, backend development
115 | - **🧪 wdi5 Context**: Testing, automation, browser testing
116 | - **🔀 MIXED Context**: Cross-platform or unclear context
117 |
118 | ### **Scoring System** ⭐
119 | - **Score 100**: Perfect matches
120 | - **Score 90+**: High relevance matches
121 | - **Score 80+**: Good matches with context boost
122 | - **Score 70+**: Moderate relevance
123 | - **Score <70**: Lower relevance (often filtered out)
124 |
125 | ### **Library Prioritization** 📚
126 | Results are ordered by relevance score, with context-aware penalties:
127 | - **CAP queries**: OpenUI5 results get 70% penalty (unless integration-related)
128 | - **wdi5 queries**: OpenUI5 results get 80% penalty (unless testing-related)
129 | - **UI5 queries**: CAP/wdi5 results get 60% penalty (unless backend/testing-related)
130 |
131 | ## 🧪 Example Test Session
132 |
133 | ```bash
134 | # Start interactive testing
135 | npx tsx test-search-interactive.ts
136 |
137 | 🔍 Enter search term (or command): wizard
138 | 🎯 Detected context: UI5
139 | ✅ Context match: YES ✅
140 | 🏆 Top result: ⭐️ **sap.f.SidePanel** (Score: 100)...
141 | 📚 Libraries found: UI5-API, UI5-Samples
142 |
143 | 🔍 Enter search term (or command): cds entity
144 | 🎯 Detected context: CAP
145 | ✅ Context match: YES ✅
146 | 🏆 Top result: ⭐️ **Core / Built-in Types** (Score: 100)...
147 | 📚 Libraries found: CAP
148 |
149 | 🔍 Enter search term (or command): all
150 | 🧪 Running all predefined test cases...
151 | [Runs comprehensive test suite]
152 | ```
153 |
154 | ## 🚀 Quick Start
155 |
156 | 1. **Test a simple search:**
157 | ```bash
158 | npx tsx test-search.ts wizard
159 | ```
160 |
161 | 2. **Run the full test suite:**
162 | ```bash
163 | npx tsx test-search-interactive.ts all
164 | ```
165 |
166 | 3. **Test HTTP API (optional):**
167 | ```bash
168 | npm run start:http
169 | # Then use test-search.http file
170 | ```
171 |
172 | ## 📈 Performance Benchmarks
173 |
174 | Typical search times:
175 | - **Simple queries**: 1-3 seconds
176 | - **Complex queries**: 2-5 seconds
177 | - **First search** (cold start): May take longer due to index loading
178 |
179 | ## 🔧 Troubleshooting
180 |
181 | **No results found:**
182 | - Check spelling
183 | - Try broader terms
184 | - Use predefined test cases to verify system works
185 |
186 | **Slow performance:**
187 | - First search loads index (normal)
188 | - Subsequent searches should be faster
189 | - Check available memory
190 |
191 | **Wrong context detection:**
192 | - Context is based on keyword analysis
193 | - Mixed contexts are normal for generic terms
194 | - Use more specific terms for better context detection
```