Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,40 @@ Wraps the `smartling-cli` binary. Three tools are available:

## File access

User files are mounted at `/smartling` inside the container. Always use `/smartling/...` paths when referring to files:
User files are mounted at `/smartling` inside the container. Always use `/smartling/...` paths when referring to local files.

### URI convention for push/pull

When pushing a file, **always** provide an explicit `<uri>` argument that strips the `/smartling` prefix:

```
# Correct
# Correct — URI is "en/strings.json" (relative, no /smartling prefix)
files push /smartling/en/strings.json en/strings.json --type json

# Wrong — URI becomes "/smartling/en/strings.json" (absolute container path)
files push /smartling/en/strings.json --type json
```

When pulling, **always** pass `--directory /smartling` so translated files are written back into the mounted directory:

# Wrong
files push ./en/strings.json
```
# Correct
files pull '**.json' -l es-ES --directory /smartling

# Wrong — files land in /app/smartling/... (inside container, not in mounted dir)
files pull '**.json' -l es-ES
```

**Why this matters:** the pull format template preserves the full URI as the output path. If the URI contains `/smartling/en/strings.json` and you pull with `--directory /smartling`, Go's `filepath.Join` produces `/smartling/smartling/en/strings_es-ES.json` (double path). Keeping URIs as relative paths (e.g. `en/strings.json`) avoids this entirely.

Use `smartling-ls` (optionally with a path) to discover what files are available before operating on them. Use `smartling-cat` to inspect file contents.

## Credentials

`SMARTLING_USER_ID`, `SMARTLING_SECRET`, and `SMARTLING_PROJECT_ID` are injected automatically via Docker env. Do not ask the user for credentials and do not include them in commands.

`SMARTLING_ACCOUNT_ID` is also injected automatically by the MCP server as a `-a` flag on every command. Most commands need account ID - if it is not set, `projects list`, `files push`, `glossaries *`, and other account-scoped operations will fail with "parameter `AccountUID` cannot be empty".

## Common task patterns

**List available files:**
Expand All @@ -41,12 +59,12 @@ smartling-cat path=/smartling/en/strings.json

**Upload a file:**
```
smartling-cli: "files push /smartling/en/strings.json --type json"
smartling-cli: "files push /smartling/en/strings.json en/strings.json --type json"
```

**Download translations:**
```
smartling-cli: "files pull '**.json' -l es-ES fr-FR"
smartling-cli: "files pull '**.json' -l es-ES -l fr-FR --directory /smartling"
```

**Check translation progress:**
Expand All @@ -63,3 +81,27 @@ smartling-cli: "projects list -a <account-id>"
```
smartling-cli: "mt translate /smartling/en/strings.json -l es-ES"
```

**List glossaries:**
```
smartling-cli: "glossaries list"
smartling-cli: "glossaries list --name 'Product Terms'"
```

**Export a glossary:**
```
smartling-cli: "glossaries export 'Product Terms' /smartling/glossary.xlsx --file-type xlsx"
smartling-cli: "glossaries export 'Product Terms' /smartling/glossary.csv --file-type csv --locale es-ES"
smartling-cli: "glossaries export 'Product Terms' /smartling/glossary.tbx --file-type tbx --tbx-version v3"
```

**Import an updated glossary:**
```
smartling-cli: "glossaries import 'Product Terms' /smartling/glossary.xlsx"
smartling-cli: "glossaries import 'Product Terms' /smartling/glossary.xlsx --archive-mode"
```

**Create a glossary:**
```
smartling-cli: "glossaries create 'Product Terms' --locale es-ES --locale fr-FR"
```
45 changes: 40 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,24 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
"-e", "SMARTLING_USER_ID",
"-e", "SMARTLING_SECRET",
"-e", "SMARTLING_PROJECT_ID",
"-e", "SMARTLING_ACCOUNT_ID",
"-v", "/absolute/path/to/your/project:/smartling",
"smartlinginc/smartling-cli-mcp"
],
"env": {
"SMARTLING_USER_ID": "your-user-id",
"SMARTLING_SECRET": "your-secret",
"SMARTLING_PROJECT_ID": "your-project-id"
"SMARTLING_PROJECT_ID": "your-project-id",
"SMARTLING_ACCOUNT_ID": "your-account-id"
}
}
}
}
```

> **Important:** The volume mount must map to `/smartling` inside the container. The `smartling-ls` and `smartling-cat` tools only work within that path.
>
> `SMARTLING_ACCOUNT_ID` is required for most commands (`files push`, `projects list`, `glossaries *`, etc.). The MCP server injects it automatically as `-a <account-id>` — it is not natively supported by the CLI as an env var.

To use a custom `smartling.yml` (e.g. with file type mappings), mount it into `/app/smartling.yml` inside the container:

Expand All @@ -75,13 +79,15 @@ Add to your project's `.claude/settings.json` or run `/mcp` in Claude Code:
"-e", "SMARTLING_USER_ID",
"-e", "SMARTLING_SECRET",
"-e", "SMARTLING_PROJECT_ID",
"-e", "SMARTLING_ACCOUNT_ID",
"-v", "/absolute/path/to/your/project:/smartling",
"smartlinginc/smartling-cli-mcp"
],
"env": {
"SMARTLING_USER_ID": "your-user-id",
"SMARTLING_SECRET": "your-secret",
"SMARTLING_PROJECT_ID": "your-project-id"
"SMARTLING_PROJECT_ID": "your-project-id",
"SMARTLING_ACCOUNT_ID": "your-account-id"
}
}
}
Expand All @@ -100,14 +106,16 @@ With a custom `smartling.yml`:
"-e", "SMARTLING_USER_ID",
"-e", "SMARTLING_SECRET",
"-e", "SMARTLING_PROJECT_ID",
"-e", "SMARTLING_ACCOUNT_ID",
"-v", "/absolute/path/to/your/project:/smartling",
"-v", "/absolute/path/to/smartling.yml:/app/smartling.yml",
"smartlinginc/smartling-cli-mcp"
],
"env": {
"SMARTLING_USER_ID": "your-user-id",
"SMARTLING_SECRET": "your-secret",
"SMARTLING_PROJECT_ID": "your-project-id"
"SMARTLING_PROJECT_ID": "your-project-id",
"SMARTLING_ACCOUNT_ID": "your-account-id"
}
}
}
Expand All @@ -120,8 +128,8 @@ Once configured, ask Claude naturally:

- *"List my Smartling projects"*
- *"Show files available for translation"*
- *"Push /smartling/en/strings.json to Smartling"*
- *"Pull Spanish translations for all JSON files"*
- *"Push /smartling/en/strings.json to Smartling with URI en/strings.json"*
- *"Pull Spanish translations for all JSON files into /smartling"*
- *"Check translation status for my project"*
- *"Machine translate /smartling/en/strings.json to French"*

Expand Down Expand Up @@ -160,6 +168,33 @@ MT (Machine Translation)
--input-directory <dir> Source directory
--output-directory <dir> Output directory

GLOSSARIES
glossaries list List glossaries in the account
--name <name> Filter by name
--output simple|table|json Output format
glossaries create <name> Create a new glossary
--locale <locale> Add a locale (repeatable)
--description <text> Optional description
--verification-mode Enable verification mode
--fallback-locale <from>:<to[,to]> Fallback locale mapping (repeatable)
glossaries export <uid|name> [file] Export glossary entries to a file
--file-type csv|xlsx|tbx Export file format (required)
--tbx-version v2|v3 TBX version (required when --file-type=tbx)
--focus-locale <locale> Focus locale for the export
--locale <locale> Include locale in export (repeatable)
--skip-entries Skip glossary entries in the export
--filter-query <text> Filter entries by free-text query
--filter-entry-state <state> Filter entries by state
--filter-locale <locale> Filter by locale ID (repeatable)
--filter-entry-uid <uid> Filter by entry UID (repeatable)
--filter-missing-translation-locale Filter: locale missing a translation
--filter-present-translation-locale Filter: locale with a translation
--filter-created-date <RFC3339> Filter by created date
--filter-last-modified-date <RFC3339> Filter by last modified date
glossaries import <uid|name> <file> Import glossary from CSV/XLSX/TBX
--archive-mode Archive entries missing from the file
--media-type <type> Override media type detection

GLOBAL FLAGS
-a, --account <account-id> Override account ID
-p, --project <project-id> Override project ID
Expand Down
69 changes: 65 additions & 4 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,31 @@ const TOOL_DESCRIPTION = `Run any smartling-cli command. Pass arguments as a sin

Run --help on any command or subcommand to see all available options, e.g. "files push --help" or "mt translate --help".

FILE URI CONVENTION
User files are mounted at /smartling inside the container.
When pushing a file, ALWAYS specify an explicit <uri> argument that strips the /smartling prefix.
This ensures the file URI stored in Smartling is a clean relative path (e.g. "en/strings.json"),
not an absolute container path (e.g. "/smartling/en/strings.json").
Failing to do so causes double-path issues on download: pulled files would land at
/smartling/smartling/... instead of /smartling/...
When pulling files, ALWAYS pass --directory /smartling so translated files are written
back into the mounted directory at the correct path.

Correct push: files push /smartling/en/strings.json en/strings.json
Wrong push: files push /smartling/en/strings.json ← URI becomes /smartling/en/strings.json
Correct pull: files pull '**.json' -l es-ES --directory /smartling
Wrong pull: files pull '**.json' -l es-ES ← files land in /app, not /smartling

GLOBAL FLAGS (supported by all commands)
-a, --account <account-id> Override account ID
-p, --project <project-id> Override project ID

ACCOUNT ID
Most commands require an account ID. Set the SMARTLING_ACCOUNT_ID environment variable
in the Docker config to inject it automatically. If not set, pass -a <account-id> explicitly.
Note: SMARTLING_ACCOUNT_ID is not natively supported by the CLI — this MCP server injects
it as a -a flag automatically when the env var is present.

PROJECTS
projects list Display all projects in the account (fields: ID, ACCOUNT, NAME, LOCALE, STATUS)
projects info Show details about the current project
Expand Down Expand Up @@ -80,25 +101,60 @@ MT (Machine Translation)
--output-directory <dir> Destination directory for translated files
--type <type> Override file type detection

GLOSSARIES
glossaries list List glossaries in the account
--name <name> Filter by name
--output simple|table|json Output format
glossaries create <name> Create a new glossary
--locale <locale> Add a locale (repeatable)
--description <text> Optional description
--verification-mode Enable verification mode
--fallback-locale <from>:<to[,to]> Fallback locale mapping (repeatable)
glossaries export <uid|name> [file] Export glossary entries to a file
--file-type csv|xlsx|tbx Export file format (required)
--tbx-version v2|v3 TBX version (required when --file-type=tbx)
--focus-locale <locale> Focus locale for the export
--locale <locale> Include locale in export (repeatable)
--skip-entries Skip glossary entries in the export
--filter-query <text> Filter: free-text query to match entries
--filter-entry-state <state> Filter: entry state to match
--filter-locale <locale> Filter: locale ID (repeatable)
--filter-entry-uid <uid> Filter: entry UID (repeatable)
--filter-missing-translation-locale <locale> Filter: locale missing a translation
--filter-present-translation-locale <locale> Filter: locale with a translation
--filter-created-date <RFC3339> Filter: created date (e.g. 2026-01-02T15:04:05Z)
--filter-last-modified-date <RFC3339> Filter: last modified date
glossaries import <uid|name> <file> Import glossary from CSV/XLSX/TBX
--archive-mode Archive entries missing from the imported file
--media-type <type> Override media type detection

EXAMPLES
projects list
projects locales --short
projects locales --format='{{if .Enabled}}{{.LocaleID}}{{end}}\n'
files list
files list '**.json' --short
files push /smartling/en/strings.json --type json
files push /smartling/en/strings.json strings/en.json
files push /smartling/en/strings.json en/strings.json --type json
files push /smartling/en/strings.json en/strings.json
files push '**.md' --type plaintext -b feature-branch
files push '**.md' --branch '@auto'
files pull '**.json' -l es-ES -l fr-FR
files pull --source
files pull '**.json' -l es-ES -l fr-FR --directory /smartling
files pull --source --directory /smartling
files delete '**.json'
files rename old/path.json new/path.json
files status
mt detect document.txt
mt detect '*.txt' --output json
mt translate document.txt -l es-ES
mt translate '*.txt' -l es -l fr --output-directory /smartling/translations/
glossaries list
glossaries list --name "Product Terms"
glossaries create "Product Terms" --locale es-ES --locale fr-FR
glossaries export "Product Terms" /smartling/glossary.xlsx --file-type xlsx
glossaries export "Product Terms" /smartling/glossary.tbx --file-type tbx --tbx-version v3
glossaries export "Product Terms" /smartling/glossary.csv --file-type csv --locale es-ES
glossaries import "Product Terms" /smartling/glossary.xlsx
glossaries import "Product Terms" /smartling/glossary.xlsx --archive-mode

Full command reference:
https://github.com/Smartling/smartling-cli/wiki/Projects-command-examples
Expand All @@ -111,6 +167,11 @@ export function createServer(execFileFn = execFileAsync) {
async function handleToolCall({ args }) {
const argsArray = splitArgs(args);

const accountId = process.env.SMARTLING_ACCOUNT_ID;
if (accountId && !argsArray.includes('-a') && !argsArray.includes('--account')) {
argsArray.unshift('-a', accountId);
}

try {
const { stdout, stderr } = await execFileFn('smartling-cli', argsArray, { env: process.env });
const output = [stdout, stderr].filter(Boolean).join('\n');
Expand Down
Loading