ndslive-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ndslive_mcp-0.1.0/.github/workflows/ci.yml +24 -0
- ndslive_mcp-0.1.0/.github/workflows/release.yml +160 -0
- ndslive_mcp-0.1.0/.gitignore +24 -0
- ndslive_mcp-0.1.0/LICENSE +28 -0
- ndslive_mcp-0.1.0/PKG-INFO +246 -0
- ndslive_mcp-0.1.0/README.md +221 -0
- ndslive_mcp-0.1.0/docs/architecture.md +81 -0
- ndslive_mcp-0.1.0/docs/jsonl-schema.md +64 -0
- ndslive_mcp-0.1.0/pyproject.toml +55 -0
- ndslive_mcp-0.1.0/scripts/build_bundle.sh +135 -0
- ndslive_mcp-0.1.0/scripts/check_needs_rebuild.sh +56 -0
- ndslive_mcp-0.1.0/scripts/deploy_bundle.sh +96 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/__init__.py +1 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/__main__.py +4 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/auth.py +76 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/build/__init__.py +0 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/build/build_index.py +341 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/build/schema.sql +80 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/cli.py +99 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/config.py +51 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/server.py +90 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/store.py +368 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/__init__.py +24 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/_common.py +30 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/modules.py +54 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/refs.py +22 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/search.py +46 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/types.py +28 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/tools/update_tool.py +38 -0
- ndslive_mcp-0.1.0/src/ndslive_mcp/update.py +226 -0
- ndslive_mcp-0.1.0/tests/test_index_pipeline.py +152 -0
- ndslive_mcp-0.1.0/tests/test_modules.py +145 -0
- ndslive_mcp-0.1.0/tests/test_smoke.py +99 -0
- ndslive_mcp-0.1.0/tests/test_update.py +146 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ['3.10', '3.11', '3.12']
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e '.[dev]'
|
|
21
|
+
- name: Lint
|
|
22
|
+
run: ruff check .
|
|
23
|
+
- name: Test
|
|
24
|
+
run: pytest -v
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
bundle_version:
|
|
7
|
+
description: "Bundle version (default: today UTC)"
|
|
8
|
+
required: false
|
|
9
|
+
indexer_extension_ref:
|
|
10
|
+
description: "Git ref of nds-live-indexer-extension to build (default: main)"
|
|
11
|
+
required: false
|
|
12
|
+
default: main
|
|
13
|
+
force:
|
|
14
|
+
description: "Rebuild even if the spec source is unchanged"
|
|
15
|
+
required: false
|
|
16
|
+
type: boolean
|
|
17
|
+
default: false
|
|
18
|
+
schedule:
|
|
19
|
+
# Weekly check, Monday 02:00 UTC. The bundle is rebuilt only when the
|
|
20
|
+
# upstream spec zip actually changed (scripts/check_needs_rebuild.sh);
|
|
21
|
+
# NDS spec releases land roughly every two months. PyPI publish runs on tag only.
|
|
22
|
+
- cron: '0 2 * * 1'
|
|
23
|
+
push:
|
|
24
|
+
tags:
|
|
25
|
+
- 'v*'
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
check:
|
|
29
|
+
# Tags only publish the PyPI package; they never trigger a bundle rebuild.
|
|
30
|
+
if: github.event_name != 'push'
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
outputs:
|
|
35
|
+
should_build: ${{ steps.decide.outputs.should_build }}
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- name: Decide whether the spec source changed
|
|
39
|
+
id: decide
|
|
40
|
+
env:
|
|
41
|
+
NDS_ARTIFACTORY_USER: ${{ secrets.NDS_ARTIFACTORY_USER }}
|
|
42
|
+
# Org secret is named *_TOKEN; our tooling reads the *_PAT env var.
|
|
43
|
+
NDS_ARTIFACTORY_PAT: ${{ secrets.NDS_ARTIFACTORY_TOKEN }}
|
|
44
|
+
ZS_BUNDLE_URL: ${{ vars.ZS_BUNDLE_URL }}
|
|
45
|
+
NDS_BUNDLE_PUBLISH_URL: ${{ vars.NDS_BUNDLE_PUBLISH_URL }}
|
|
46
|
+
run: |
|
|
47
|
+
if [ "${{ inputs.force }}" = "true" ]; then
|
|
48
|
+
echo "forced rebuild requested"
|
|
49
|
+
echo "should_build=true" >> "$GITHUB_OUTPUT"
|
|
50
|
+
else
|
|
51
|
+
result="$(bash scripts/check_needs_rebuild.sh)"
|
|
52
|
+
echo "check_needs_rebuild -> $result"
|
|
53
|
+
echo "should_build=$result" >> "$GITHUB_OUTPUT"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
build-bundle:
|
|
57
|
+
needs: check
|
|
58
|
+
if: needs.check.outputs.should_build == 'true'
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
permissions:
|
|
61
|
+
contents: read
|
|
62
|
+
env:
|
|
63
|
+
NDS_ARTIFACTORY_USER: ${{ secrets.NDS_ARTIFACTORY_USER }}
|
|
64
|
+
# Org secret is named *_TOKEN; our tooling reads the *_PAT env var.
|
|
65
|
+
NDS_ARTIFACTORY_PAT: ${{ secrets.NDS_ARTIFACTORY_TOKEN }}
|
|
66
|
+
ZS_BUNDLE_URL: ${{ vars.ZS_BUNDLE_URL }}
|
|
67
|
+
NDS_BUNDLE_PUBLISH_URL: ${{ vars.NDS_BUNDLE_PUBLISH_URL }}
|
|
68
|
+
# Clones the private ndsev doc/compat repos in build_bundle.sh.
|
|
69
|
+
NDS_REPOS_TOKEN: ${{ secrets.INDEXER_EXTENSION_TOKEN }}
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
|
|
73
|
+
- uses: actions/setup-java@v4
|
|
74
|
+
with:
|
|
75
|
+
distribution: temurin
|
|
76
|
+
# JDK 21 runs Gradle + the foojay-resolver plugin (needs JVM 17+);
|
|
77
|
+
# JDK 11 is the indexer-extension's compile toolchain (matches zserio).
|
|
78
|
+
java-version: |
|
|
79
|
+
11
|
|
80
|
+
21
|
|
81
|
+
|
|
82
|
+
- uses: actions/setup-python@v5
|
|
83
|
+
with:
|
|
84
|
+
python-version: '3.12'
|
|
85
|
+
|
|
86
|
+
- name: Install MCP package
|
|
87
|
+
run: pip install -e .
|
|
88
|
+
|
|
89
|
+
- name: Check out indexer-extension (private build dependency)
|
|
90
|
+
uses: actions/checkout@v4
|
|
91
|
+
with:
|
|
92
|
+
# Separate private repo (same org, but the default GITHUB_TOKEN is
|
|
93
|
+
# scoped to THIS repo only and can't read siblings), so a PAT/App
|
|
94
|
+
# token with Contents:Read on the indexer-extension repo is required.
|
|
95
|
+
repository: ${{ vars.INDEXER_EXTENSION_REPO || 'ndsev/nds-live-indexer-extension' }}
|
|
96
|
+
ref: ${{ inputs.indexer_extension_ref || 'main' }}
|
|
97
|
+
path: indexer-extension
|
|
98
|
+
token: ${{ secrets.INDEXER_EXTENSION_TOKEN }}
|
|
99
|
+
|
|
100
|
+
- name: Install zserio compiler (provides zserio.jar)
|
|
101
|
+
run: |
|
|
102
|
+
# The prod spec zip no longer bundles zserio.jar; the pip package
|
|
103
|
+
# ships the matching compiler at site-packages/zserio/compiler/zserio.jar.
|
|
104
|
+
pip install "zserio==2.18.1"
|
|
105
|
+
ZSERIO_JAR="$(python -c 'import zserio, os; print(os.path.join(os.path.dirname(zserio.__file__), "compiler", "zserio.jar"))')"
|
|
106
|
+
test -f "$ZSERIO_JAR"
|
|
107
|
+
echo "ZSERIO_JAR=$ZSERIO_JAR" >> "$GITHUB_ENV"
|
|
108
|
+
# The indexer-extension build resolves zserio via compileOnly fileTree('libs').
|
|
109
|
+
ZSV="$(java -jar "$ZSERIO_JAR" --version 2>/dev/null | head -1 | awk '{print $NF}')"
|
|
110
|
+
mkdir -p indexer-extension/libs
|
|
111
|
+
cp "$ZSERIO_JAR" "indexer-extension/libs/zserio-${ZSV}.jar"
|
|
112
|
+
echo "staged zserio $ZSV from $ZSERIO_JAR"
|
|
113
|
+
|
|
114
|
+
- uses: gradle/actions/setup-gradle@v4
|
|
115
|
+
with:
|
|
116
|
+
# The indexer-extension ships no wrapper; pin an 8.x line its shadow
|
|
117
|
+
# plugin (com.gradleup.shadow 8.3.5) supports. Gradle runs on JDK 21
|
|
118
|
+
# and compiles the extension with the JDK 11 toolchain.
|
|
119
|
+
gradle-version: '8.10.2'
|
|
120
|
+
|
|
121
|
+
- name: Build indexer-extension shadow jar
|
|
122
|
+
working-directory: indexer-extension
|
|
123
|
+
run: |
|
|
124
|
+
# Gradle runs on JDK 21 (default); compile the extension with the
|
|
125
|
+
# pre-installed JDK 11 toolchain so foojay needs no download.
|
|
126
|
+
gradle --no-daemon shadowJar \
|
|
127
|
+
-Dorg.gradle.java.installations.fromEnv=JAVA_HOME_11_X64,JAVA_HOME_21_X64
|
|
128
|
+
echo "INDEXER_JAR=$(ls "$PWD"/build/libs/*-all.jar | head -1)" >> $GITHUB_ENV
|
|
129
|
+
|
|
130
|
+
- name: Build bundle
|
|
131
|
+
env:
|
|
132
|
+
BUNDLE_VERSION: ${{ inputs.bundle_version }}
|
|
133
|
+
run: bash scripts/build_bundle.sh
|
|
134
|
+
|
|
135
|
+
- name: Deploy bundle to Artifactory
|
|
136
|
+
run: bash scripts/deploy_bundle.sh
|
|
137
|
+
|
|
138
|
+
- uses: actions/upload-artifact@v4
|
|
139
|
+
with:
|
|
140
|
+
name: bundle-${{ inputs.bundle_version || github.run_id }}
|
|
141
|
+
path: |
|
|
142
|
+
.work/ndslive-mcp.zip
|
|
143
|
+
.work/ndslive-mcp.json
|
|
144
|
+
|
|
145
|
+
publish-pypi:
|
|
146
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
147
|
+
runs-on: ubuntu-latest
|
|
148
|
+
permissions:
|
|
149
|
+
# A permissions block replaces ALL defaults, so checkout needs contents:read
|
|
150
|
+
# spelled out here — otherwise actions/checkout gets "repository not found".
|
|
151
|
+
contents: read
|
|
152
|
+
id-token: write # PyPI Trusted Publishing (OIDC, no long-lived tokens)
|
|
153
|
+
steps:
|
|
154
|
+
- uses: actions/checkout@v4
|
|
155
|
+
- uses: actions/setup-python@v5
|
|
156
|
+
with:
|
|
157
|
+
python-version: '3.12'
|
|
158
|
+
- run: pip install build
|
|
159
|
+
- run: python -m build
|
|
160
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.tox/
|
|
10
|
+
# Python build output at repo root only — not the src/ndslive_mcp/build/ subpackage.
|
|
11
|
+
/build/
|
|
12
|
+
/dist/
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
.env
|
|
16
|
+
.DS_Store
|
|
17
|
+
*.swp
|
|
18
|
+
|
|
19
|
+
# local artifacts during development
|
|
20
|
+
.work/
|
|
21
|
+
.cache/
|
|
22
|
+
out/
|
|
23
|
+
# local spec source zips — NDS Protected Material, never commit
|
|
24
|
+
_ext/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Navigation Data Standard e.V.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ndslive-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local MCP server for searching and exploring the NDS.Live specification
|
|
5
|
+
Author: NDS Association
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: mcp,navigation,nds,ndslive,zserio
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: keyring>=24
|
|
18
|
+
Requires-Dist: mcp>=1.2
|
|
19
|
+
Requires-Dist: platformdirs>=4
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# ndslive-mcp
|
|
27
|
+
|
|
28
|
+
A locally-installable MCP server giving agents — Claude Code, IDE assistants, scripts — structured search and lookup over the NDS.Live specification.
|
|
29
|
+
|
|
30
|
+
## What it gives you
|
|
31
|
+
|
|
32
|
+
Once installed and authenticated, your MCP host gains nine tools:
|
|
33
|
+
|
|
34
|
+
| Tool | What it does |
|
|
35
|
+
|------|--------------|
|
|
36
|
+
| `search_spec` | Full-text search over symbol names, qnames, doc comments. Filter by kind / module / version. |
|
|
37
|
+
| `search_docs` | Full-text search over the bundled `documentation.nds.live` and `best-practices.nds.live` markdown. |
|
|
38
|
+
| `get_type` | Resolve a fully-qualified name → kind, module, version, source file, line, doc, fields. |
|
|
39
|
+
| `find_references` | Every place a type is referenced, by field name and source location. |
|
|
40
|
+
| `list_modules` | Modules in the bundle, optionally filtered by category (common / feature / attribute / service / reference). |
|
|
41
|
+
| `get_module` | Module metadata: category, deps, top-level types. |
|
|
42
|
+
| `get_module_versions` | Every version of a module the bundle has indexed. |
|
|
43
|
+
| `compare_versions` | Diff between two versions of the same module: added / removed / changed types. |
|
|
44
|
+
| `update_index` | Force a refresh against Artifactory. Live-swap; no server restart. |
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pipx install ndslive-mcp # public PyPI; no NDS gate on the code itself
|
|
50
|
+
ndslive-mcp auth # save your NDS Artifactory PAT to OS keyring
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then register with your MCP host. For Claude Code:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add ndslive -- ndslive-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For other MCP hosts, run `ndslive-mcp serve` as a stdio subprocess.
|
|
60
|
+
|
|
61
|
+
## How auth works
|
|
62
|
+
|
|
63
|
+
The Python package is public on PyPI — anyone can install. The **bundle** (the actual NDS.Live spec content: SQLite index, raw schemas, docs) is gated behind NDS Artifactory PAT auth. On first run, the server tries to download the bundle; if no PAT is saved it logs a warning and refuses to answer queries until you run `ndslive-mcp auth`.
|
|
64
|
+
|
|
65
|
+
PATs are stored in the OS keyring (macOS Keychain / Linux Secret Service / Windows Credential Locker). They never live in plaintext on disk.
|
|
66
|
+
|
|
67
|
+
For headless / CI usage:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
NDS_ARTIFACTORY_USER=u NDS_ARTIFACTORY_PAT=p ndslive-mcp serve
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
These env vars take precedence over the keyring.
|
|
74
|
+
|
|
75
|
+
## How updates work
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
server start ──► HEAD ndslive-mcp.json on Artifactory (~100 ms)
|
|
79
|
+
│
|
|
80
|
+
┌──────┴──────┐
|
|
81
|
+
same version newer version
|
|
82
|
+
│ │
|
|
83
|
+
│ ▼
|
|
84
|
+
│ GET bundle.zip
|
|
85
|
+
│ verify sha256
|
|
86
|
+
│ extract → ~/.cache/ndslive-mcp/versions/<v>/
|
|
87
|
+
│ atomic-swap `current` symlink
|
|
88
|
+
│ │
|
|
89
|
+
└──────┬───────┘
|
|
90
|
+
▼
|
|
91
|
+
open index.sqlite (read-only)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **Atomic**: a half-downloaded bundle never becomes the live one — the symlink only flips after sha256 verification.
|
|
95
|
+
- **Rollback-friendly**: previous version dirs stay on disk.
|
|
96
|
+
- **No background polling**: check on startup and on explicit `update_index` tool call only.
|
|
97
|
+
|
|
98
|
+
Manual refresh:
|
|
99
|
+
```python
|
|
100
|
+
update_index(force=true)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## How it's built
|
|
104
|
+
|
|
105
|
+
The bundle is built off-band by CI and published to Artifactory:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
spec sources ──► zserio.jar + indexer-extension ──► symbols.jsonl
|
|
109
|
+
│
|
|
110
|
+
+ nds.live.compatibility/*.yaml (categories)
|
|
111
|
+
+ documentation.nds.live + best-practices (markdown)
|
|
112
|
+
│
|
|
113
|
+
▼
|
|
114
|
+
build_index.py
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
index.sqlite (FTS5)
|
|
118
|
+
│
|
|
119
|
+
▼
|
|
120
|
+
ndslive-mcp.zip + ndslive-mcp.json
|
|
121
|
+
│
|
|
122
|
+
▼
|
|
123
|
+
NDS Artifactory — same folder as the spec zip (gated)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Java only runs at build time. Clients are pure Python — no JRE required.
|
|
127
|
+
|
|
128
|
+
See [`docs/architecture.md`](docs/architecture.md) for the full design and [`docs/jsonl-schema.md`](docs/jsonl-schema.md) for the JSONL contract.
|
|
129
|
+
|
|
130
|
+
## Build and test locally
|
|
131
|
+
|
|
132
|
+
### Prerequisites
|
|
133
|
+
|
|
134
|
+
- **Python 3.10+** — for the server itself, the test suite, and the index build step.
|
|
135
|
+
- **Java 11+** — only needed if you want to build a fresh bundle end-to-end (the indexer extension runs the zserio compiler). The installed server does not need a JRE.
|
|
136
|
+
- **A checkout of [`nds-live-indexer-extension`](https://github.com/ndsev/nds-live-indexer-extension)** alongside this repo, if you want to rebuild the indexer.
|
|
137
|
+
- **The zserio compiler jar** via `pip install zserio==2.18.1` (the prod spec zip does not bundle it). The jar lands at `…/site-packages/zserio/compiler/zserio.jar`.
|
|
138
|
+
- **A spec bundle zip** for the indexer to consume (e.g. `ndslive.zip` from the NDS compatibility-build pipeline; unpacks to `ndslive/` with `all.zs` at its root).
|
|
139
|
+
|
|
140
|
+
### Install and run tests
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e '.[dev]'
|
|
144
|
+
pytest # hermetic — no Java, no network, no Artifactory
|
|
145
|
+
ruff check .
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The test suite uses synthesized JSONL fixtures and `httpx.MockTransport` so it has no external dependencies. CI runs the same commands across Python 3.10 / 3.11 / 3.12.
|
|
149
|
+
|
|
150
|
+
### Run the server against an existing bundle
|
|
151
|
+
|
|
152
|
+
If you already have a `ndslive-mcp.zip` on disk (e.g. from CI or a colleague), point the cache at it and serve in `--offline` mode so it skips the startup Artifactory check:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Extract the bundle into a versioned cache dir
|
|
156
|
+
mkdir -p ~/.cache/ndslive-mcp/versions/local
|
|
157
|
+
unzip -q <path-to-bundle>.zip -d ~/.cache/ndslive-mcp/versions/local/
|
|
158
|
+
|
|
159
|
+
# Point `current` at it
|
|
160
|
+
ln -sfn ~/.cache/ndslive-mcp/versions/local ~/.cache/ndslive-mcp/current
|
|
161
|
+
|
|
162
|
+
# Serve — no auth required, no network call
|
|
163
|
+
ndslive-mcp serve --offline
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
You can iterate on tool definitions in `src/ndslive_mcp/tools/`, restart, and the new code picks up the existing index.
|
|
167
|
+
|
|
168
|
+
### Build a bundle end-to-end
|
|
169
|
+
|
|
170
|
+
Useful for testing the full pipeline before pushing. Requires Java 11+ and a built indexer JAR.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# 0. Get the zserio compiler jar (shared by the indexer build and the index run)
|
|
174
|
+
pip install zserio==2.18.1
|
|
175
|
+
ZSERIO_JAR=$(python -c 'import zserio, os; print(os.path.join(os.path.dirname(zserio.__file__), "compiler", "zserio.jar"))')
|
|
176
|
+
|
|
177
|
+
# 1. Build the indexer JAR once (or after extension changes)
|
|
178
|
+
cd ../nds-live-indexer-extension
|
|
179
|
+
mkdir -p libs && cp "$ZSERIO_JAR" "libs/zserio-2.18.1.jar" # satisfies compileOnly fileTree('libs')
|
|
180
|
+
gradle shadowJar
|
|
181
|
+
|
|
182
|
+
# 2. Build a bundle in this repo using a local spec zip
|
|
183
|
+
cd ../ndslive-mcp
|
|
184
|
+
ZSERIO_JAR=$ZSERIO_JAR \
|
|
185
|
+
INDEXER_JAR=../nds-live-indexer-extension/build/libs/nds-live-indexer-extension-*-all.jar \
|
|
186
|
+
LOCAL_SPEC_ZIP=../_ext/ndslive.zip \
|
|
187
|
+
SKIP_DOCS=1 \
|
|
188
|
+
bash scripts/build_bundle.sh
|
|
189
|
+
# → .work/ndslive-mcp.zip
|
|
190
|
+
# → .work/ndslive-mcp.json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then run `ndslive-mcp serve --offline` against the produced bundle (see previous section).
|
|
194
|
+
|
|
195
|
+
Env-var overrides that short-circuit external fetches for offline iteration:
|
|
196
|
+
|
|
197
|
+
| Variable | Effect |
|
|
198
|
+
|----------|--------|
|
|
199
|
+
| `LOCAL_SPEC_ZIP` | Use a local spec zip instead of fetching from Artifactory. |
|
|
200
|
+
| `SKIP_DOCS` | Skip cloning `documentation.nds.live` + `best-practices.nds.live` + `nds.live.compatibility` (faster, but no doc FTS and no module categories). |
|
|
201
|
+
| `BUNDLE_VERSION` | Override the version string in `ndslive-mcp.json`; defaults to today UTC. |
|
|
202
|
+
| `WORK` | Work directory; defaults to `./.work`. |
|
|
203
|
+
|
|
204
|
+
Without these overrides, `build_bundle.sh` needs `NDS_ARTIFACTORY_USER` / `NDS_ARTIFACTORY_PAT` to download the spec zip from the NDS compatibility-build pipeline.
|
|
205
|
+
|
|
206
|
+
### Smoke-check what landed in the bundle
|
|
207
|
+
|
|
208
|
+
The SQLite index inside the bundle is queryable directly. A quick sanity check after a build:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
python -c "
|
|
212
|
+
from pathlib import Path
|
|
213
|
+
from ndslive_mcp.store import Store
|
|
214
|
+
s = Store(Path('.work/out/index.sqlite'))
|
|
215
|
+
print('modules:', len(s.list_modules()))
|
|
216
|
+
print('lane versions:', s.get_module_versions('lane'))
|
|
217
|
+
print('sample search:', [r.qname for r in s.search('LaneGroup', limit=3)])
|
|
218
|
+
"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Deploy
|
|
222
|
+
|
|
223
|
+
CI runs `scripts/deploy_bundle.sh` automatically as the final step of the release workflow. To deploy by hand (emergency push, or to test the deploy path without merging to main):
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# 1. Build the bundle, embedding the production publish URL
|
|
227
|
+
NDS_BUNDLE_PUBLISH_URL=https://artifactory.nds-association.org/artifactory/<repo>/<path> \
|
|
228
|
+
INDEXER_JAR=../nds-live-indexer-extension/build/libs/nds-live-indexer-extension-*-all.jar \
|
|
229
|
+
NDS_ARTIFACTORY_USER=$USER \
|
|
230
|
+
NDS_ARTIFACTORY_PAT=$ART_PAT \
|
|
231
|
+
bash scripts/build_bundle.sh
|
|
232
|
+
|
|
233
|
+
# 2. Dry-run to confirm the upload destinations
|
|
234
|
+
DRY_RUN=1 bash scripts/deploy_bundle.sh
|
|
235
|
+
|
|
236
|
+
# 3. Real upload — bundle first, then ndslive-mcp.json (order matters: keeps clients consistent)
|
|
237
|
+
NDS_ARTIFACTORY_USER=$USER NDS_ARTIFACTORY_PAT=$ART_PAT \
|
|
238
|
+
NDS_BUNDLE_PUBLISH_URL=https://artifactory.nds-association.org/artifactory/<repo>/<path> \
|
|
239
|
+
bash scripts/deploy_bundle.sh
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The deploy script refuses to run if `ndslive-mcp.json`'s embedded `url` doesn't match `NDS_BUNDLE_PUBLISH_URL` — that mismatch means the build was done with a stale URL and clients would 404.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
The `ndslive-mcp` software is licensed under [BSD-3-Clause](LICENSE) — the same as the [`ndslive-setup`](https://pypi.org/project/ndslive-setup/) installer. The license covers this software (the "hull") only. The NDS.Live specification content delivered as the bundle is **NDS Protected Material**, not part of this package, and remains gated behind NDS Artifactory authentication and governed by your NDS Member Agreement or NDS.Live Evaluation License.
|