nativ-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.
- nativ_mcp-0.1.0/.gitignore +28 -0
- nativ_mcp-0.1.0/LICENSE +21 -0
- nativ_mcp-0.1.0/PKG-INFO +248 -0
- nativ_mcp-0.1.0/README.md +221 -0
- nativ_mcp-0.1.0/pyproject.toml +52 -0
- nativ_mcp-0.1.0/src/nativ_mcp/__init__.py +3 -0
- nativ_mcp-0.1.0/src/nativ_mcp/server.py +660 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
.Python
|
|
6
|
+
build/
|
|
7
|
+
develop-eggs/
|
|
8
|
+
dist/
|
|
9
|
+
downloads/
|
|
10
|
+
eggs/
|
|
11
|
+
.eggs/
|
|
12
|
+
lib/
|
|
13
|
+
lib64/
|
|
14
|
+
parts/
|
|
15
|
+
sdist/
|
|
16
|
+
var/
|
|
17
|
+
wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
*.egg
|
|
20
|
+
.env
|
|
21
|
+
.venv
|
|
22
|
+
venv/
|
|
23
|
+
ENV/
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
.DS_Store
|
nativ_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nativ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
nativ_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nativ-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Nativ — AI-powered localization platform. Translate text, manage translation memory, and access style guides from any MCP-compatible AI tool.
|
|
5
|
+
Project-URL: Homepage, https://usenativ.com
|
|
6
|
+
Project-URL: Repository, https://github.com/nativ-ai/nativ-mcp
|
|
7
|
+
Project-URL: Documentation, https://github.com/nativ-ai/nativ-mcp#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/nativ-ai/nativ-mcp/issues
|
|
9
|
+
Author-email: Nativ <hello@usenativ.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,i18n,l10n,localization,mcp,nativ,translation,translation-memory
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Internationalization
|
|
22
|
+
Classifier: Topic :: Software Development :: Localization
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: mcp[cli]>=1.2.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Nativ MCP Server
|
|
29
|
+
|
|
30
|
+
AI-powered localization for any MCP-compatible tool — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.sh), [Windsurf](https://codeium.com/windsurf), and more.
|
|
31
|
+
|
|
32
|
+
[Nativ](https://usenativ.com) is a localization platform that uses AI to translate content while respecting your brand voice, translation memory, glossaries, and style guides. This MCP server brings Nativ's full localization engine into your AI coding workflow.
|
|
33
|
+
|
|
34
|
+
<a href="https://smithery.ai/server/@nativ-ai/nativ-mcp"><img alt="Smithery" src="https://smithery.ai/badge/@nativ-ai/nativ-mcp"></a>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why use Nativ via MCP?
|
|
39
|
+
|
|
40
|
+
- **Translate in-context** — localize strings, copy, and content directly from your editor without switching to a browser
|
|
41
|
+
- **Translation Memory aware** — every translation checks your TM first, ensuring consistency across your project
|
|
42
|
+
- **Brand voice built-in** — your team's tone, formality, and style guides are applied automatically
|
|
43
|
+
- **Review and approve** — add approved translations to TM from your editor, building quality over time
|
|
44
|
+
- **Multi-format** — JSON, CSV, Markdown, or freeform text — Nativ handles it all
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### 1. Get a Nativ API Key
|
|
49
|
+
|
|
50
|
+
Sign up at [dashboard.usenativ.com](https://dashboard.usenativ.com), go to **Settings → API Keys**, and create a key. It looks like `nativ_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`.
|
|
51
|
+
|
|
52
|
+
### 2. Install
|
|
53
|
+
|
|
54
|
+
#### Claude Code / Claude Desktop
|
|
55
|
+
|
|
56
|
+
Add to your MCP configuration (`~/.claude/claude_desktop_config.json`):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"nativ": {
|
|
62
|
+
"command": "uvx",
|
|
63
|
+
"args": ["nativ-mcp"],
|
|
64
|
+
"env": {
|
|
65
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Cursor
|
|
73
|
+
|
|
74
|
+
Add to your Cursor MCP settings (`.cursor/mcp.json` in your project or global config):
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"nativ": {
|
|
80
|
+
"command": "uvx",
|
|
81
|
+
"args": ["nativ-mcp"],
|
|
82
|
+
"env": {
|
|
83
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Windsurf
|
|
91
|
+
|
|
92
|
+
Add to your Windsurf MCP configuration:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"nativ": {
|
|
98
|
+
"command": "uvx",
|
|
99
|
+
"args": ["nativ-mcp"],
|
|
100
|
+
"env": {
|
|
101
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> **Note:** `uvx` runs the package directly from PyPI without needing a manual install. If you prefer, install it first with `pip install nativ-mcp` and use `"command": "nativ-mcp"` instead.
|
|
109
|
+
|
|
110
|
+
### 3. Use it
|
|
111
|
+
|
|
112
|
+
Ask your AI assistant things like:
|
|
113
|
+
|
|
114
|
+
- *"Translate 'Welcome back!' to French and German"*
|
|
115
|
+
- *"Check our translation memory for existing translations of 'Sign up'"*
|
|
116
|
+
- *"What are our style guides for localization?"*
|
|
117
|
+
- *"Localize these i18n strings to all configured languages"*
|
|
118
|
+
- *"Review this German translation against our TM and brand voice"*
|
|
119
|
+
|
|
120
|
+
## Tools
|
|
121
|
+
|
|
122
|
+
| Tool | Description |
|
|
123
|
+
|------|-------------|
|
|
124
|
+
| `translate` | Translate text using the full localization engine (TM, style guides, brand voice, glossary) |
|
|
125
|
+
| `translate_batch` | Translate multiple texts to a target language in one call |
|
|
126
|
+
| `search_translation_memory` | Fuzzy-search the translation memory for existing translations |
|
|
127
|
+
| `add_translation_memory_entry` | Add an approved translation to TM for future reuse |
|
|
128
|
+
| `get_languages` | List all configured languages with formality and style settings |
|
|
129
|
+
| `get_translation_memory_stats` | Get TM statistics — total entries, sources, and breakdown |
|
|
130
|
+
| `get_style_guides` | List all style guides with their content and status |
|
|
131
|
+
| `get_brand_voice` | Get the brand voice prompt that shapes all translations |
|
|
132
|
+
|
|
133
|
+
## Resources
|
|
134
|
+
|
|
135
|
+
| URI | Description |
|
|
136
|
+
|-----|-------------|
|
|
137
|
+
| `nativ://languages` | Configured languages (JSON) |
|
|
138
|
+
| `nativ://style-guides` | All style guides (JSON) |
|
|
139
|
+
| `nativ://brand-prompt` | Brand voice prompt (JSON) |
|
|
140
|
+
| `nativ://tm/stats` | Translation memory statistics (JSON) |
|
|
141
|
+
|
|
142
|
+
## Prompts
|
|
143
|
+
|
|
144
|
+
| Prompt | Description |
|
|
145
|
+
|--------|-------------|
|
|
146
|
+
| `localize-content` | Guided workflow to localize content into target languages |
|
|
147
|
+
| `review-translation` | Review a translation against TM, style guides, and brand voice |
|
|
148
|
+
| `batch-localize-strings` | Batch-localize i18n strings with structured output |
|
|
149
|
+
|
|
150
|
+
## Examples
|
|
151
|
+
|
|
152
|
+
### Translate a marketing headline
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
You: Translate "The future of luxury, delivered" to French and Japanese
|
|
156
|
+
|
|
157
|
+
AI: [calls translate tool for each language]
|
|
158
|
+
|
|
159
|
+
Translation (French): "L'avenir du luxe, livré chez vous"
|
|
160
|
+
TM Match: 0% — new translation, no prior TM entries
|
|
161
|
+
Rationale: "Livré chez vous" adds a personal touch absent from the literal
|
|
162
|
+
"livré", aligning with the brand's premium yet approachable voice.
|
|
163
|
+
|
|
164
|
+
Translation (Japanese): "ラグジュアリーの未来を、あなたの元へ"
|
|
165
|
+
TM Match: 45% partial — similar pattern found in TM from brand_voice source
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Check existing translations
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
You: Do we have translations for "Add to cart" in our TM?
|
|
172
|
+
|
|
173
|
+
AI: [calls search_translation_memory]
|
|
174
|
+
|
|
175
|
+
TM Search Results for "Add to cart" (3 matches):
|
|
176
|
+
- 95% [strong] "Add to cart" → "Ajouter au panier" (source: approved)
|
|
177
|
+
- 95% [strong] "Add to cart" → "In den Warenkorb" (source: brand_voice)
|
|
178
|
+
- 72% [partial] "Add items to cart" → "Ajouter des articles" (source: phrase_tm)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Batch localize i18n strings
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
You: Localize these to French:
|
|
185
|
+
- "Sign up"
|
|
186
|
+
- "Log in"
|
|
187
|
+
- "Forgot password?"
|
|
188
|
+
- "Continue with Google"
|
|
189
|
+
|
|
190
|
+
AI: [calls translate_batch]
|
|
191
|
+
|
|
192
|
+
Batch translation to French (4 items):
|
|
193
|
+
1. "Sign up" → "S'inscrire" (TM 100%)
|
|
194
|
+
2. "Log in" → "Se connecter" (TM 100%)
|
|
195
|
+
3. "Forgot password?" → "Mot de passe oublié ?" (TM 92%)
|
|
196
|
+
4. "Continue with Google" → "Continuer avec Google" (TM 85%)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
| Environment Variable | Required | Description |
|
|
202
|
+
|---------------------|----------|-------------|
|
|
203
|
+
| `NATIV_API_KEY` | Yes | Your Nativ API key (`nativ_xxx...`) |
|
|
204
|
+
| `NATIV_API_URL` | No | API base URL (defaults to `https://api.usenativ.com`) |
|
|
205
|
+
|
|
206
|
+
## How It Works
|
|
207
|
+
|
|
208
|
+
This MCP server acts as a bridge between your AI coding assistant and the Nativ API:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
┌─────────────────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
212
|
+
│ Claude / Cursor / │────▶│ Nativ MCP │────▶│ Nativ API │
|
|
213
|
+
│ Windsurf / etc. │◀────│ Server │◀────│ (Translation, │
|
|
214
|
+
│ │ │ (stdio) │ │ TM, Styles) │
|
|
215
|
+
└─────────────────────┘ └──────────────┘ └─────────────────┘
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The MCP server runs locally via stdio. It authenticates with your API key and calls the Nativ REST API on your behalf. Your AI assistant sees Nativ's tools, resources, and prompts as native capabilities.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Clone the repo
|
|
224
|
+
git clone https://github.com/nativ-ai/nativ-mcp.git
|
|
225
|
+
cd nativ-mcp
|
|
226
|
+
|
|
227
|
+
# Set up environment
|
|
228
|
+
uv venv
|
|
229
|
+
source .venv/bin/activate
|
|
230
|
+
uv pip install -e ".[dev]"
|
|
231
|
+
|
|
232
|
+
# Run the server (for testing)
|
|
233
|
+
NATIV_API_KEY=nativ_xxx nativ-mcp
|
|
234
|
+
|
|
235
|
+
# Run with MCP Inspector
|
|
236
|
+
NATIV_API_KEY=nativ_xxx npx @modelcontextprotocol/inspector uv run nativ-mcp
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT — see [LICENSE](LICENSE).
|
|
242
|
+
|
|
243
|
+
## Links
|
|
244
|
+
|
|
245
|
+
- [Nativ Platform](https://usenativ.com)
|
|
246
|
+
- [Nativ Dashboard](https://dashboard.usenativ.com)
|
|
247
|
+
- [MCP Protocol](https://modelcontextprotocol.io)
|
|
248
|
+
- [Report Issues](https://github.com/nativ-ai/nativ-mcp/issues)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Nativ MCP Server
|
|
2
|
+
|
|
3
|
+
AI-powered localization for any MCP-compatible tool — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.sh), [Windsurf](https://codeium.com/windsurf), and more.
|
|
4
|
+
|
|
5
|
+
[Nativ](https://usenativ.com) is a localization platform that uses AI to translate content while respecting your brand voice, translation memory, glossaries, and style guides. This MCP server brings Nativ's full localization engine into your AI coding workflow.
|
|
6
|
+
|
|
7
|
+
<a href="https://smithery.ai/server/@nativ-ai/nativ-mcp"><img alt="Smithery" src="https://smithery.ai/badge/@nativ-ai/nativ-mcp"></a>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why use Nativ via MCP?
|
|
12
|
+
|
|
13
|
+
- **Translate in-context** — localize strings, copy, and content directly from your editor without switching to a browser
|
|
14
|
+
- **Translation Memory aware** — every translation checks your TM first, ensuring consistency across your project
|
|
15
|
+
- **Brand voice built-in** — your team's tone, formality, and style guides are applied automatically
|
|
16
|
+
- **Review and approve** — add approved translations to TM from your editor, building quality over time
|
|
17
|
+
- **Multi-format** — JSON, CSV, Markdown, or freeform text — Nativ handles it all
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Get a Nativ API Key
|
|
22
|
+
|
|
23
|
+
Sign up at [dashboard.usenativ.com](https://dashboard.usenativ.com), go to **Settings → API Keys**, and create a key. It looks like `nativ_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`.
|
|
24
|
+
|
|
25
|
+
### 2. Install
|
|
26
|
+
|
|
27
|
+
#### Claude Code / Claude Desktop
|
|
28
|
+
|
|
29
|
+
Add to your MCP configuration (`~/.claude/claude_desktop_config.json`):
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"nativ": {
|
|
35
|
+
"command": "uvx",
|
|
36
|
+
"args": ["nativ-mcp"],
|
|
37
|
+
"env": {
|
|
38
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Cursor
|
|
46
|
+
|
|
47
|
+
Add to your Cursor MCP settings (`.cursor/mcp.json` in your project or global config):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"nativ": {
|
|
53
|
+
"command": "uvx",
|
|
54
|
+
"args": ["nativ-mcp"],
|
|
55
|
+
"env": {
|
|
56
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Windsurf
|
|
64
|
+
|
|
65
|
+
Add to your Windsurf MCP configuration:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"nativ": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["nativ-mcp"],
|
|
73
|
+
"env": {
|
|
74
|
+
"NATIV_API_KEY": "nativ_your_api_key_here"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Note:** `uvx` runs the package directly from PyPI without needing a manual install. If you prefer, install it first with `pip install nativ-mcp` and use `"command": "nativ-mcp"` instead.
|
|
82
|
+
|
|
83
|
+
### 3. Use it
|
|
84
|
+
|
|
85
|
+
Ask your AI assistant things like:
|
|
86
|
+
|
|
87
|
+
- *"Translate 'Welcome back!' to French and German"*
|
|
88
|
+
- *"Check our translation memory for existing translations of 'Sign up'"*
|
|
89
|
+
- *"What are our style guides for localization?"*
|
|
90
|
+
- *"Localize these i18n strings to all configured languages"*
|
|
91
|
+
- *"Review this German translation against our TM and brand voice"*
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
| Tool | Description |
|
|
96
|
+
|------|-------------|
|
|
97
|
+
| `translate` | Translate text using the full localization engine (TM, style guides, brand voice, glossary) |
|
|
98
|
+
| `translate_batch` | Translate multiple texts to a target language in one call |
|
|
99
|
+
| `search_translation_memory` | Fuzzy-search the translation memory for existing translations |
|
|
100
|
+
| `add_translation_memory_entry` | Add an approved translation to TM for future reuse |
|
|
101
|
+
| `get_languages` | List all configured languages with formality and style settings |
|
|
102
|
+
| `get_translation_memory_stats` | Get TM statistics — total entries, sources, and breakdown |
|
|
103
|
+
| `get_style_guides` | List all style guides with their content and status |
|
|
104
|
+
| `get_brand_voice` | Get the brand voice prompt that shapes all translations |
|
|
105
|
+
|
|
106
|
+
## Resources
|
|
107
|
+
|
|
108
|
+
| URI | Description |
|
|
109
|
+
|-----|-------------|
|
|
110
|
+
| `nativ://languages` | Configured languages (JSON) |
|
|
111
|
+
| `nativ://style-guides` | All style guides (JSON) |
|
|
112
|
+
| `nativ://brand-prompt` | Brand voice prompt (JSON) |
|
|
113
|
+
| `nativ://tm/stats` | Translation memory statistics (JSON) |
|
|
114
|
+
|
|
115
|
+
## Prompts
|
|
116
|
+
|
|
117
|
+
| Prompt | Description |
|
|
118
|
+
|--------|-------------|
|
|
119
|
+
| `localize-content` | Guided workflow to localize content into target languages |
|
|
120
|
+
| `review-translation` | Review a translation against TM, style guides, and brand voice |
|
|
121
|
+
| `batch-localize-strings` | Batch-localize i18n strings with structured output |
|
|
122
|
+
|
|
123
|
+
## Examples
|
|
124
|
+
|
|
125
|
+
### Translate a marketing headline
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
You: Translate "The future of luxury, delivered" to French and Japanese
|
|
129
|
+
|
|
130
|
+
AI: [calls translate tool for each language]
|
|
131
|
+
|
|
132
|
+
Translation (French): "L'avenir du luxe, livré chez vous"
|
|
133
|
+
TM Match: 0% — new translation, no prior TM entries
|
|
134
|
+
Rationale: "Livré chez vous" adds a personal touch absent from the literal
|
|
135
|
+
"livré", aligning with the brand's premium yet approachable voice.
|
|
136
|
+
|
|
137
|
+
Translation (Japanese): "ラグジュアリーの未来を、あなたの元へ"
|
|
138
|
+
TM Match: 45% partial — similar pattern found in TM from brand_voice source
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Check existing translations
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
You: Do we have translations for "Add to cart" in our TM?
|
|
145
|
+
|
|
146
|
+
AI: [calls search_translation_memory]
|
|
147
|
+
|
|
148
|
+
TM Search Results for "Add to cart" (3 matches):
|
|
149
|
+
- 95% [strong] "Add to cart" → "Ajouter au panier" (source: approved)
|
|
150
|
+
- 95% [strong] "Add to cart" → "In den Warenkorb" (source: brand_voice)
|
|
151
|
+
- 72% [partial] "Add items to cart" → "Ajouter des articles" (source: phrase_tm)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Batch localize i18n strings
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
You: Localize these to French:
|
|
158
|
+
- "Sign up"
|
|
159
|
+
- "Log in"
|
|
160
|
+
- "Forgot password?"
|
|
161
|
+
- "Continue with Google"
|
|
162
|
+
|
|
163
|
+
AI: [calls translate_batch]
|
|
164
|
+
|
|
165
|
+
Batch translation to French (4 items):
|
|
166
|
+
1. "Sign up" → "S'inscrire" (TM 100%)
|
|
167
|
+
2. "Log in" → "Se connecter" (TM 100%)
|
|
168
|
+
3. "Forgot password?" → "Mot de passe oublié ?" (TM 92%)
|
|
169
|
+
4. "Continue with Google" → "Continuer avec Google" (TM 85%)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Configuration
|
|
173
|
+
|
|
174
|
+
| Environment Variable | Required | Description |
|
|
175
|
+
|---------------------|----------|-------------|
|
|
176
|
+
| `NATIV_API_KEY` | Yes | Your Nativ API key (`nativ_xxx...`) |
|
|
177
|
+
| `NATIV_API_URL` | No | API base URL (defaults to `https://api.usenativ.com`) |
|
|
178
|
+
|
|
179
|
+
## How It Works
|
|
180
|
+
|
|
181
|
+
This MCP server acts as a bridge between your AI coding assistant and the Nativ API:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
┌─────────────────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
185
|
+
│ Claude / Cursor / │────▶│ Nativ MCP │────▶│ Nativ API │
|
|
186
|
+
│ Windsurf / etc. │◀────│ Server │◀────│ (Translation, │
|
|
187
|
+
│ │ │ (stdio) │ │ TM, Styles) │
|
|
188
|
+
└─────────────────────┘ └──────────────┘ └─────────────────┘
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The MCP server runs locally via stdio. It authenticates with your API key and calls the Nativ REST API on your behalf. Your AI assistant sees Nativ's tools, resources, and prompts as native capabilities.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Clone the repo
|
|
197
|
+
git clone https://github.com/nativ-ai/nativ-mcp.git
|
|
198
|
+
cd nativ-mcp
|
|
199
|
+
|
|
200
|
+
# Set up environment
|
|
201
|
+
uv venv
|
|
202
|
+
source .venv/bin/activate
|
|
203
|
+
uv pip install -e ".[dev]"
|
|
204
|
+
|
|
205
|
+
# Run the server (for testing)
|
|
206
|
+
NATIV_API_KEY=nativ_xxx nativ-mcp
|
|
207
|
+
|
|
208
|
+
# Run with MCP Inspector
|
|
209
|
+
NATIV_API_KEY=nativ_xxx npx @modelcontextprotocol/inspector uv run nativ-mcp
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT — see [LICENSE](LICENSE).
|
|
215
|
+
|
|
216
|
+
## Links
|
|
217
|
+
|
|
218
|
+
- [Nativ Platform](https://usenativ.com)
|
|
219
|
+
- [Nativ Dashboard](https://dashboard.usenativ.com)
|
|
220
|
+
- [MCP Protocol](https://modelcontextprotocol.io)
|
|
221
|
+
- [Report Issues](https://github.com/nativ-ai/nativ-mcp/issues)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nativ-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for Nativ — AI-powered localization platform. Translate text, manage translation memory, and access style guides from any MCP-compatible AI tool."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Nativ", email = "hello@usenativ.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = [
|
|
12
|
+
"mcp",
|
|
13
|
+
"localization",
|
|
14
|
+
"translation",
|
|
15
|
+
"i18n",
|
|
16
|
+
"l10n",
|
|
17
|
+
"nativ",
|
|
18
|
+
"ai",
|
|
19
|
+
"translation-memory",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development :: Internationalization",
|
|
31
|
+
"Topic :: Software Development :: Localization",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"mcp[cli]>=1.2.0",
|
|
35
|
+
"httpx>=0.27.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
nativ-mcp = "nativ_mcp.server:main"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://usenativ.com"
|
|
43
|
+
Repository = "https://github.com/nativ-ai/nativ-mcp"
|
|
44
|
+
Documentation = "https://github.com/nativ-ai/nativ-mcp#readme"
|
|
45
|
+
Issues = "https://github.com/nativ-ai/nativ-mcp/issues"
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["hatchling"]
|
|
49
|
+
build-backend = "hatchling.build"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/nativ_mcp"]
|
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nativ MCP Server
|
|
3
|
+
|
|
4
|
+
Exposes Nativ's AI localization platform as an MCP server, enabling
|
|
5
|
+
translation, translation memory, and style guide access from any
|
|
6
|
+
MCP-compatible AI tool (Claude Code, Cursor, Windsurf, etc.).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
from mcp.server.fastmcp import FastMCP
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
|
|
19
|
+
logger = logging.getLogger("nativ-mcp")
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Nativ API client
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
DEFAULT_API_URL = "https://api.usenativ.com"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NativClient:
|
|
29
|
+
"""Thin async wrapper around the Nativ REST API."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, api_key: str, base_url: str | None = None):
|
|
32
|
+
self.base_url = (base_url or DEFAULT_API_URL).rstrip("/")
|
|
33
|
+
self.headers = {
|
|
34
|
+
"X-API-Key": api_key,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"User-Agent": "nativ-mcp/0.1.0",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async def _request(
|
|
40
|
+
self,
|
|
41
|
+
method: str,
|
|
42
|
+
path: str,
|
|
43
|
+
*,
|
|
44
|
+
json_body: dict | None = None,
|
|
45
|
+
params: dict | None = None,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
48
|
+
resp = await client.request(
|
|
49
|
+
method,
|
|
50
|
+
f"{self.base_url}{path}",
|
|
51
|
+
headers=self.headers,
|
|
52
|
+
json=json_body,
|
|
53
|
+
params=params,
|
|
54
|
+
)
|
|
55
|
+
if resp.status_code == 402:
|
|
56
|
+
raise RuntimeError("Insufficient Nativ credits. Top up at https://dashboard.usenativ.com")
|
|
57
|
+
resp.raise_for_status()
|
|
58
|
+
return resp.json()
|
|
59
|
+
|
|
60
|
+
# -- Translation --------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
async def translate(
|
|
63
|
+
self,
|
|
64
|
+
text: str,
|
|
65
|
+
language: str,
|
|
66
|
+
*,
|
|
67
|
+
language_code: str | None = None,
|
|
68
|
+
source_language: str = "English",
|
|
69
|
+
source_language_code: str = "en",
|
|
70
|
+
context: str | None = None,
|
|
71
|
+
glossary: str | None = None,
|
|
72
|
+
formality: str | None = None,
|
|
73
|
+
max_characters: int | None = None,
|
|
74
|
+
include_tm_info: bool = True,
|
|
75
|
+
backtranslate: bool = False,
|
|
76
|
+
include_rationale: bool = True,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
body: dict[str, Any] = {
|
|
79
|
+
"text": text,
|
|
80
|
+
"language": language,
|
|
81
|
+
"source_language": source_language,
|
|
82
|
+
"source_language_code": source_language_code,
|
|
83
|
+
"tool": "api",
|
|
84
|
+
"include_tm_info": include_tm_info,
|
|
85
|
+
"backtranslate": backtranslate,
|
|
86
|
+
"include_rationale": include_rationale,
|
|
87
|
+
}
|
|
88
|
+
if language_code:
|
|
89
|
+
body["language_code"] = language_code
|
|
90
|
+
if context:
|
|
91
|
+
body["context"] = context
|
|
92
|
+
if glossary:
|
|
93
|
+
body["glossary"] = glossary
|
|
94
|
+
if formality:
|
|
95
|
+
body["formality"] = formality
|
|
96
|
+
if max_characters is not None:
|
|
97
|
+
body["max_characters"] = max_characters
|
|
98
|
+
return await self._request("POST", "/text/culturalize", json_body=body)
|
|
99
|
+
|
|
100
|
+
# -- Languages ----------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
async def get_languages(self) -> dict[str, Any]:
|
|
103
|
+
return await self._request("GET", "/user/languages")
|
|
104
|
+
|
|
105
|
+
# -- Translation Memory -------------------------------------------------
|
|
106
|
+
|
|
107
|
+
async def search_tm(
|
|
108
|
+
self,
|
|
109
|
+
query: str,
|
|
110
|
+
*,
|
|
111
|
+
source_lang: str = "en",
|
|
112
|
+
target_lang: str | None = None,
|
|
113
|
+
score_cutoff: float = 0.0,
|
|
114
|
+
limit: int = 20,
|
|
115
|
+
) -> dict[str, Any]:
|
|
116
|
+
params: dict[str, Any] = {
|
|
117
|
+
"query": query,
|
|
118
|
+
"source_lang": source_lang,
|
|
119
|
+
"score_cutoff": score_cutoff,
|
|
120
|
+
"limit": limit,
|
|
121
|
+
}
|
|
122
|
+
if target_lang:
|
|
123
|
+
params["target_lang"] = target_lang
|
|
124
|
+
return await self._request("GET", "/master-tm/fuzzy-search", params=params)
|
|
125
|
+
|
|
126
|
+
async def list_tm_entries(
|
|
127
|
+
self,
|
|
128
|
+
*,
|
|
129
|
+
source_lang: str | None = None,
|
|
130
|
+
target_lang: str | None = None,
|
|
131
|
+
search: str | None = None,
|
|
132
|
+
limit: int = 50,
|
|
133
|
+
offset: int = 0,
|
|
134
|
+
) -> dict[str, Any]:
|
|
135
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
136
|
+
if source_lang:
|
|
137
|
+
params["source_lang"] = source_lang
|
|
138
|
+
if target_lang:
|
|
139
|
+
params["target_lang"] = target_lang
|
|
140
|
+
if search:
|
|
141
|
+
params["search"] = search
|
|
142
|
+
return await self._request("GET", "/master-tm/entries", params=params)
|
|
143
|
+
|
|
144
|
+
async def add_tm_entry(
|
|
145
|
+
self,
|
|
146
|
+
source_text: str,
|
|
147
|
+
target_text: str,
|
|
148
|
+
source_language_code: str,
|
|
149
|
+
target_language_code: str,
|
|
150
|
+
*,
|
|
151
|
+
source_name: str | None = None,
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
body = {
|
|
154
|
+
"source_text": source_text,
|
|
155
|
+
"target_text": target_text,
|
|
156
|
+
"source_language_code": source_language_code,
|
|
157
|
+
"target_language_code": target_language_code,
|
|
158
|
+
"information_source": "manual",
|
|
159
|
+
}
|
|
160
|
+
if source_name:
|
|
161
|
+
body["source_name"] = source_name
|
|
162
|
+
return await self._request("POST", "/master-tm/entries", json_body=body)
|
|
163
|
+
|
|
164
|
+
async def get_tm_stats(self) -> dict[str, Any]:
|
|
165
|
+
return await self._request("GET", "/master-tm/stats")
|
|
166
|
+
|
|
167
|
+
# -- Style Guides -------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
async def list_style_guides(self) -> dict[str, Any]:
|
|
170
|
+
return await self._request("GET", "/style-guide")
|
|
171
|
+
|
|
172
|
+
async def get_brand_prompt(self) -> dict[str, Any]:
|
|
173
|
+
return await self._request("GET", "/style-guide/prompt")
|
|
174
|
+
|
|
175
|
+
async def get_combined_prompt(self) -> dict[str, Any]:
|
|
176
|
+
return await self._request("GET", "/style-guide/combined")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Helpers
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
def _get_client() -> NativClient:
|
|
184
|
+
"""Build a NativClient from env vars. Raises on missing key."""
|
|
185
|
+
api_key = os.environ.get("NATIV_API_KEY")
|
|
186
|
+
if not api_key:
|
|
187
|
+
raise RuntimeError(
|
|
188
|
+
"NATIV_API_KEY environment variable is required. "
|
|
189
|
+
"Create one at https://dashboard.usenativ.com → Settings → API Keys"
|
|
190
|
+
)
|
|
191
|
+
base_url = os.environ.get("NATIV_API_URL")
|
|
192
|
+
return NativClient(api_key, base_url)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _fmt_json(data: Any) -> str:
|
|
196
|
+
"""Pretty-print JSON for LLM consumption."""
|
|
197
|
+
return json.dumps(data, indent=2, ensure_ascii=False, default=str)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
# MCP Server
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
mcp = FastMCP(
|
|
205
|
+
"nativ",
|
|
206
|
+
instructions=(
|
|
207
|
+
"Nativ is an AI-powered localization platform. Use the available tools to "
|
|
208
|
+
"translate text, search translation memory, manage TM entries, and access "
|
|
209
|
+
"style guides. Always use the user's configured languages and respect their "
|
|
210
|
+
"brand voice and style settings."
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ===================== TOOLS =====================
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@mcp.tool()
|
|
219
|
+
async def translate(
|
|
220
|
+
text: str,
|
|
221
|
+
target_language: str,
|
|
222
|
+
target_language_code: str = "",
|
|
223
|
+
source_language: str = "English",
|
|
224
|
+
source_language_code: str = "en",
|
|
225
|
+
context: str = "",
|
|
226
|
+
glossary: str = "",
|
|
227
|
+
formality: str = "",
|
|
228
|
+
max_characters: int = 0,
|
|
229
|
+
backtranslate: bool = False,
|
|
230
|
+
) -> str:
|
|
231
|
+
"""Translate text using Nativ's AI localization engine.
|
|
232
|
+
|
|
233
|
+
Uses the team's translation memory, style guides, and brand voice
|
|
234
|
+
automatically. Returns the translation along with TM match info and
|
|
235
|
+
rationale.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
text: The text to translate.
|
|
239
|
+
target_language: Full target language name (e.g. "French", "German", "Japanese").
|
|
240
|
+
target_language_code: Optional ISO language code (e.g. "fr", "de", "ja").
|
|
241
|
+
source_language: Source language name. Defaults to English.
|
|
242
|
+
source_language_code: Source language code. Defaults to "en".
|
|
243
|
+
context: Optional context to guide the translation (e.g. "marketing headline for Gen Z audience").
|
|
244
|
+
glossary: Optional inline glossary as CSV (e.g. "term,translation\\nbrand,marque").
|
|
245
|
+
formality: Tone override — one of: very_informal, informal, neutral, formal, very_formal.
|
|
246
|
+
max_characters: Optional strict character limit for the translation output.
|
|
247
|
+
backtranslate: If true, also returns a back-translation to verify intent.
|
|
248
|
+
"""
|
|
249
|
+
client = _get_client()
|
|
250
|
+
result = await client.translate(
|
|
251
|
+
text=text,
|
|
252
|
+
language=target_language,
|
|
253
|
+
language_code=target_language_code or None,
|
|
254
|
+
source_language=source_language,
|
|
255
|
+
source_language_code=source_language_code,
|
|
256
|
+
context=context or None,
|
|
257
|
+
glossary=glossary or None,
|
|
258
|
+
formality=formality or None,
|
|
259
|
+
max_characters=max_characters if max_characters > 0 else None,
|
|
260
|
+
backtranslate=backtranslate,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
parts = [f"**Translation ({target_language}):** {result.get('translated_text', '')}"]
|
|
264
|
+
|
|
265
|
+
tm = result.get("tm_match")
|
|
266
|
+
if tm and tm.get("score", 0) > 0:
|
|
267
|
+
parts.append(f"\n**TM Match:** {tm['score']:.0f}% ({tm.get('match_type', 'unknown')}) — source: {tm.get('tm_source', 'N/A')}")
|
|
268
|
+
if tm.get("source_text"):
|
|
269
|
+
parts.append(f" TM reference: \"{tm['source_text']}\" → \"{tm.get('target_text', '')}\"")
|
|
270
|
+
|
|
271
|
+
if result.get("backtranslation"):
|
|
272
|
+
parts.append(f"\n**Back-translation:** {result['backtranslation']}")
|
|
273
|
+
|
|
274
|
+
if result.get("rationale"):
|
|
275
|
+
parts.append(f"\n**Rationale:** {result['rationale']}")
|
|
276
|
+
|
|
277
|
+
return "\n".join(parts)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@mcp.tool()
|
|
281
|
+
async def translate_batch(
|
|
282
|
+
texts: list[str],
|
|
283
|
+
target_language: str,
|
|
284
|
+
target_language_code: str = "",
|
|
285
|
+
source_language: str = "English",
|
|
286
|
+
source_language_code: str = "en",
|
|
287
|
+
context: str = "",
|
|
288
|
+
formality: str = "",
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Translate multiple texts to a single target language.
|
|
291
|
+
|
|
292
|
+
Useful for localizing lists of strings, UI labels, or i18n files.
|
|
293
|
+
Each text is translated individually using the team's TM and style guides.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
texts: List of texts to translate.
|
|
297
|
+
target_language: Full target language name (e.g. "French").
|
|
298
|
+
target_language_code: Optional ISO language code.
|
|
299
|
+
source_language: Source language name. Defaults to English.
|
|
300
|
+
source_language_code: Source language code. Defaults to "en".
|
|
301
|
+
context: Optional context to guide all translations.
|
|
302
|
+
formality: Tone override for all translations.
|
|
303
|
+
"""
|
|
304
|
+
client = _get_client()
|
|
305
|
+
results = []
|
|
306
|
+
for i, text in enumerate(texts):
|
|
307
|
+
try:
|
|
308
|
+
result = await client.translate(
|
|
309
|
+
text=text,
|
|
310
|
+
language=target_language,
|
|
311
|
+
language_code=target_language_code or None,
|
|
312
|
+
source_language=source_language,
|
|
313
|
+
source_language_code=source_language_code,
|
|
314
|
+
context=context or None,
|
|
315
|
+
formality=formality or None,
|
|
316
|
+
include_tm_info=True,
|
|
317
|
+
backtranslate=False,
|
|
318
|
+
include_rationale=False,
|
|
319
|
+
)
|
|
320
|
+
translated = result.get("translated_text", "")
|
|
321
|
+
tm = result.get("tm_match")
|
|
322
|
+
tm_note = ""
|
|
323
|
+
if tm and tm.get("score", 0) > 0:
|
|
324
|
+
tm_note = f" (TM {tm['score']:.0f}%)"
|
|
325
|
+
results.append(f"{i+1}. \"{text}\" → \"{translated}\"{tm_note}")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
results.append(f"{i+1}. \"{text}\" → ERROR: {e}")
|
|
328
|
+
|
|
329
|
+
header = f"**Batch translation to {target_language}** ({len(texts)} items):\n"
|
|
330
|
+
return header + "\n".join(results)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@mcp.tool()
|
|
334
|
+
async def search_translation_memory(
|
|
335
|
+
query: str,
|
|
336
|
+
source_language_code: str = "en",
|
|
337
|
+
target_language_code: str = "",
|
|
338
|
+
min_score: float = 0.0,
|
|
339
|
+
limit: int = 10,
|
|
340
|
+
) -> str:
|
|
341
|
+
"""Search the translation memory for existing translations.
|
|
342
|
+
|
|
343
|
+
Use this to check if translations already exist before creating new ones,
|
|
344
|
+
or to find reference translations for consistency.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
query: Text to search for in the translation memory.
|
|
348
|
+
source_language_code: Source language code (default: "en").
|
|
349
|
+
target_language_code: Optional target language code to filter results.
|
|
350
|
+
min_score: Minimum fuzzy match score (0-100). Default 0 returns all.
|
|
351
|
+
limit: Maximum number of results (default 10).
|
|
352
|
+
"""
|
|
353
|
+
client = _get_client()
|
|
354
|
+
result = await client.search_tm(
|
|
355
|
+
query=query,
|
|
356
|
+
source_lang=source_language_code,
|
|
357
|
+
target_lang=target_language_code or None,
|
|
358
|
+
score_cutoff=min_score,
|
|
359
|
+
limit=limit,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
matches = result.get("matches", [])
|
|
363
|
+
if not matches:
|
|
364
|
+
return f"No translation memory matches found for \"{query}\"."
|
|
365
|
+
|
|
366
|
+
lines = [f"**TM Search Results** for \"{query}\" ({len(matches)} matches):\n"]
|
|
367
|
+
for m in matches:
|
|
368
|
+
lines.append(
|
|
369
|
+
f"- **{m['score']:.0f}%** [{m.get('match_type', '')}] "
|
|
370
|
+
f"\"{m['source_text']}\" → \"{m['target_text']}\" "
|
|
371
|
+
f"(source: {m.get('information_source', 'N/A')})"
|
|
372
|
+
)
|
|
373
|
+
return "\n".join(lines)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@mcp.tool()
|
|
377
|
+
async def add_translation_memory_entry(
|
|
378
|
+
source_text: str,
|
|
379
|
+
target_text: str,
|
|
380
|
+
source_language_code: str,
|
|
381
|
+
target_language_code: str,
|
|
382
|
+
name: str = "",
|
|
383
|
+
) -> str:
|
|
384
|
+
"""Add a new entry to the translation memory.
|
|
385
|
+
|
|
386
|
+
Use this to store approved translations so they are reused in future
|
|
387
|
+
localizations.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
source_text: The original text.
|
|
391
|
+
target_text: The approved translation.
|
|
392
|
+
source_language_code: Source language code (e.g. "en").
|
|
393
|
+
target_language_code: Target language code (e.g. "fr-FR").
|
|
394
|
+
name: Optional label for this entry (e.g. "homepage hero copy").
|
|
395
|
+
"""
|
|
396
|
+
client = _get_client()
|
|
397
|
+
result = await client.add_tm_entry(
|
|
398
|
+
source_text=source_text,
|
|
399
|
+
target_text=target_text,
|
|
400
|
+
source_language_code=source_language_code,
|
|
401
|
+
target_language_code=target_language_code,
|
|
402
|
+
source_name=name or None,
|
|
403
|
+
)
|
|
404
|
+
return (
|
|
405
|
+
f"Added TM entry: \"{source_text}\" ({source_language_code}) "
|
|
406
|
+
f"→ \"{target_text}\" ({target_language_code})"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@mcp.tool()
|
|
411
|
+
async def get_languages() -> str:
|
|
412
|
+
"""Get all languages configured for the Nativ workspace.
|
|
413
|
+
|
|
414
|
+
Returns language names, codes, formality settings, and custom style
|
|
415
|
+
directives for each language.
|
|
416
|
+
"""
|
|
417
|
+
client = _get_client()
|
|
418
|
+
result = await client.get_languages()
|
|
419
|
+
languages = result.get("languages", [])
|
|
420
|
+
|
|
421
|
+
if not languages:
|
|
422
|
+
return "No languages configured. Set up languages at https://dashboard.usenativ.com"
|
|
423
|
+
|
|
424
|
+
lines = [f"**Configured Languages** ({len(languages)}):\n"]
|
|
425
|
+
for lang in languages:
|
|
426
|
+
formality = lang.get("formality") or "neutral"
|
|
427
|
+
parts = [f"- **{lang['language']}** (`{lang['language_code']}`) — formality: {formality}"]
|
|
428
|
+
if lang.get("custom_style"):
|
|
429
|
+
parts.append(f" Custom style: {lang['custom_style']}")
|
|
430
|
+
lines.append("\n".join(parts))
|
|
431
|
+
return "\n".join(lines)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@mcp.tool()
|
|
435
|
+
async def get_translation_memory_stats() -> str:
|
|
436
|
+
"""Get statistics about the translation memory.
|
|
437
|
+
|
|
438
|
+
Shows total entries, enabled/disabled counts, and breakdown by source type.
|
|
439
|
+
"""
|
|
440
|
+
client = _get_client()
|
|
441
|
+
stats = await client.get_tm_stats()
|
|
442
|
+
|
|
443
|
+
lines = [
|
|
444
|
+
"**Translation Memory Statistics:**\n",
|
|
445
|
+
f"- Total entries: {stats.get('total', 0)}",
|
|
446
|
+
f"- Enabled: {stats.get('enabled', 0)}",
|
|
447
|
+
f"- Disabled: {stats.get('disabled', 0)}",
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
by_source = stats.get("by_source", {})
|
|
451
|
+
if by_source:
|
|
452
|
+
lines.append("\n**By Source:**")
|
|
453
|
+
source_labels = {
|
|
454
|
+
"brand_voice": "Brand Voice",
|
|
455
|
+
"phrase_tm": "Phrase TM",
|
|
456
|
+
"phrase_tb": "Phrase Term Base",
|
|
457
|
+
"user_glossary": "User Glossary",
|
|
458
|
+
"approved": "Approved Translations",
|
|
459
|
+
"manual": "Manual Entries",
|
|
460
|
+
}
|
|
461
|
+
for src, counts in by_source.items():
|
|
462
|
+
label = source_labels.get(src, src)
|
|
463
|
+
lines.append(f"- {label}: {counts.get('total', 0)} entries ({counts.get('enabled', 0)} enabled)")
|
|
464
|
+
|
|
465
|
+
return "\n".join(lines)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@mcp.tool()
|
|
469
|
+
async def get_style_guides() -> str:
|
|
470
|
+
"""Get all style guides configured for the workspace.
|
|
471
|
+
|
|
472
|
+
Returns the titles, content, and enabled status of each style guide.
|
|
473
|
+
"""
|
|
474
|
+
client = _get_client()
|
|
475
|
+
result = await client.list_style_guides()
|
|
476
|
+
guides = result.get("guides", [])
|
|
477
|
+
|
|
478
|
+
if not guides:
|
|
479
|
+
return "No custom style guides configured. Create them at https://dashboard.usenativ.com → Settings → Style Guides"
|
|
480
|
+
|
|
481
|
+
lines = [f"**Style Guides** ({len(guides)}):\n"]
|
|
482
|
+
for g in guides:
|
|
483
|
+
status = "enabled" if g.get("is_enabled") else "disabled"
|
|
484
|
+
lines.append(f"### {g.get('title', 'Untitled')} ({status})")
|
|
485
|
+
content = g.get("content", "")
|
|
486
|
+
if len(content) > 500:
|
|
487
|
+
content = content[:500] + "..."
|
|
488
|
+
lines.append(content)
|
|
489
|
+
lines.append("")
|
|
490
|
+
return "\n".join(lines)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@mcp.tool()
|
|
494
|
+
async def get_brand_voice() -> str:
|
|
495
|
+
"""Get the brand voice prompt — the core localization personality.
|
|
496
|
+
|
|
497
|
+
This is the master prompt that shapes all translations. It captures
|
|
498
|
+
the brand's tone, personality, terminology, and localization guidelines.
|
|
499
|
+
"""
|
|
500
|
+
client = _get_client()
|
|
501
|
+
result = await client.get_brand_prompt()
|
|
502
|
+
|
|
503
|
+
if not result.get("exists"):
|
|
504
|
+
return "No brand voice prompt configured. Set one up at https://dashboard.usenativ.com → Settings → Style Guides"
|
|
505
|
+
|
|
506
|
+
prompt = result.get("prompt", "")
|
|
507
|
+
if len(prompt) > 2000:
|
|
508
|
+
return f"**Brand Voice Prompt:**\n\n{prompt[:2000]}...\n\n(Truncated — full prompt has {len(prompt)} characters)"
|
|
509
|
+
return f"**Brand Voice Prompt:**\n\n{prompt}"
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
# ===================== RESOURCES =====================
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@mcp.resource("nativ://languages")
|
|
516
|
+
async def resource_languages() -> str:
|
|
517
|
+
"""Configured languages with formality and style settings."""
|
|
518
|
+
client = _get_client()
|
|
519
|
+
result = await client.get_languages()
|
|
520
|
+
return _fmt_json(result)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@mcp.resource("nativ://style-guides")
|
|
524
|
+
async def resource_style_guides() -> str:
|
|
525
|
+
"""All style guides including enabled/disabled status."""
|
|
526
|
+
client = _get_client()
|
|
527
|
+
result = await client.list_style_guides()
|
|
528
|
+
return _fmt_json(result)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@mcp.resource("nativ://brand-prompt")
|
|
532
|
+
async def resource_brand_prompt() -> str:
|
|
533
|
+
"""The master brand voice prompt used for all translations."""
|
|
534
|
+
client = _get_client()
|
|
535
|
+
result = await client.get_brand_prompt()
|
|
536
|
+
return _fmt_json(result)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
@mcp.resource("nativ://tm/stats")
|
|
540
|
+
async def resource_tm_stats() -> str:
|
|
541
|
+
"""Translation memory statistics and source breakdown."""
|
|
542
|
+
client = _get_client()
|
|
543
|
+
result = await client.get_tm_stats()
|
|
544
|
+
return _fmt_json(result)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
# ===================== PROMPTS =====================
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@mcp.prompt()
|
|
551
|
+
def localize_content(
|
|
552
|
+
content: str,
|
|
553
|
+
target_languages: str = "",
|
|
554
|
+
context: str = "",
|
|
555
|
+
) -> str:
|
|
556
|
+
"""Localize content into target languages using Nativ.
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
content: The content to localize.
|
|
560
|
+
target_languages: Comma-separated list of target languages (e.g. "French, German, Japanese"). Leave empty to use all configured languages.
|
|
561
|
+
context: Optional context about the content (e.g. "marketing email subject line").
|
|
562
|
+
"""
|
|
563
|
+
parts = [
|
|
564
|
+
"You are helping localize content using Nativ's AI localization platform.\n",
|
|
565
|
+
"## Instructions\n",
|
|
566
|
+
"1. First, call `get_languages` to see which languages are configured and their formality/style settings.",
|
|
567
|
+
"2. Call `get_brand_voice` to understand the brand's localization personality.",
|
|
568
|
+
]
|
|
569
|
+
|
|
570
|
+
if target_languages:
|
|
571
|
+
parts.append(f"3. Translate the content below into: **{target_languages}**")
|
|
572
|
+
else:
|
|
573
|
+
parts.append("3. Translate the content below into **all configured languages**.")
|
|
574
|
+
|
|
575
|
+
parts.extend([
|
|
576
|
+
"4. For each language, use the `translate` tool with the appropriate formality and context.",
|
|
577
|
+
"5. Present results in a clear table: Source | Language | Translation | TM Match %",
|
|
578
|
+
"",
|
|
579
|
+
"## Content to Localize\n",
|
|
580
|
+
content,
|
|
581
|
+
])
|
|
582
|
+
|
|
583
|
+
if context:
|
|
584
|
+
parts.extend(["", f"## Context\n", context])
|
|
585
|
+
|
|
586
|
+
return "\n".join(parts)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
@mcp.prompt()
|
|
590
|
+
def review_translation(
|
|
591
|
+
source_text: str,
|
|
592
|
+
translated_text: str,
|
|
593
|
+
target_language: str,
|
|
594
|
+
) -> str:
|
|
595
|
+
"""Review a translation for quality and consistency with TM and style guides.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
source_text: The original text.
|
|
599
|
+
translated_text: The translation to review.
|
|
600
|
+
target_language: The target language name.
|
|
601
|
+
"""
|
|
602
|
+
return "\n".join([
|
|
603
|
+
"You are reviewing a translation for quality using Nativ.\n",
|
|
604
|
+
"## Instructions\n",
|
|
605
|
+
"1. Call `get_brand_voice` to understand the brand's localization guidelines.",
|
|
606
|
+
"2. Call `get_style_guides` to check for any relevant style rules.",
|
|
607
|
+
f"3. Call `search_translation_memory` with the source text to find existing TM matches for {target_language}.",
|
|
608
|
+
"4. Compare the translation against TM matches and style guides.",
|
|
609
|
+
"5. Provide feedback on:",
|
|
610
|
+
" - Accuracy: Does it preserve the original meaning?",
|
|
611
|
+
" - Consistency: Does it match existing TM entries?",
|
|
612
|
+
" - Brand voice: Does it follow style guide rules?",
|
|
613
|
+
" - Tone: Is the formality level appropriate?",
|
|
614
|
+
"6. If the translation is good, suggest adding it to TM with `add_translation_memory_entry`.",
|
|
615
|
+
"",
|
|
616
|
+
f"## Source Text\n{source_text}",
|
|
617
|
+
"",
|
|
618
|
+
f"## Translation ({target_language})\n{translated_text}",
|
|
619
|
+
])
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@mcp.prompt()
|
|
623
|
+
def batch_localize_strings(
|
|
624
|
+
strings: str,
|
|
625
|
+
target_languages: str = "",
|
|
626
|
+
format_hint: str = "json",
|
|
627
|
+
) -> str:
|
|
628
|
+
"""Batch-localize i18n strings for a software project.
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
strings: The strings to localize — can be JSON, CSV, or one-per-line.
|
|
632
|
+
target_languages: Comma-separated target languages. Leave empty for all configured.
|
|
633
|
+
format_hint: Expected output format: "json", "csv", or "plain".
|
|
634
|
+
"""
|
|
635
|
+
return "\n".join([
|
|
636
|
+
"You are batch-localizing i18n strings using Nativ.\n",
|
|
637
|
+
"## Instructions\n",
|
|
638
|
+
"1. Call `get_languages` to see configured languages.",
|
|
639
|
+
f"2. Parse the strings below (format hint: {format_hint}).",
|
|
640
|
+
"3. Use `translate_batch` for each target language to translate all strings at once.",
|
|
641
|
+
f"4. Output results in **{format_hint}** format, preserving the original keys/structure.",
|
|
642
|
+
"5. If any TM matches are >= 90%, note them — these are well-established translations.",
|
|
643
|
+
"",
|
|
644
|
+
f"## Target Languages\n{target_languages if target_languages else '(all configured languages)'}",
|
|
645
|
+
"",
|
|
646
|
+
f"## Strings\n```\n{strings}\n```",
|
|
647
|
+
])
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
# ---------------------------------------------------------------------------
|
|
651
|
+
# Entry point
|
|
652
|
+
# ---------------------------------------------------------------------------
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def main():
|
|
656
|
+
mcp.run(transport="stdio")
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
if __name__ == "__main__":
|
|
660
|
+
main()
|