diff --git a/README.md b/README.md index f95bed6..3eaf53c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Sammlung von Skills für Claude und Cursor. Jeder Skill ist ein abgeschlossener | Skill | Kurzbeschreibung | Version | Doku | Download | Erweiterung (.mcpb) | Cursor-Plugin | Cursor 1-Klick | |---|---|---|---|---|---|---|---| -| [gridcraft-mcp](skills/gridcraft-mcp/) | Excel-Arbeitsmappen (.xlsx, .csv) mit dem GridCraft-MCP-Server erstellen, öffnen, ändern, berechnen und prüfen. Verwenden, wenn GridCraft-Werkzeuge verbunden sind oder der Nutzer GridCraft nennt; typisch für Kalkulationen, Stücklisten (BOM), Listen, Diagramme, PDF-Export. Nicht für eingebaute xlsx-Hilfen ohne GridCraft-Server. | 0.3.0 | [README](skills/gridcraft-mcp/README.md) | [gridcraft-mcp-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-v0.3.0.zip) | [gridcraft-mcp-v0.3.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-v0.3.0.mcpb) | [gridcraft-mcp-cursor-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-cursor-v0.3.0.zip) | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=gridcraft&config=eyJncmlkY3JhZnQiOnsiYXJncyI6WyJtY3AiXSwiY29tbWFuZCI6IkM6XFxQcm9ncmFtIEZpbGVzXFxHcmlkQ3JhZnRcXGdyaWRjcmFmdC1jbGkuZXhlIn19) | -| [wordcraft-mcp](skills/wordcraft-mcp/) | Word-Dokumente (.docx, .odt, .md, PDF) mit dem WordCraft-MCP-Server erstellen, bearbeiten und prüfen (inkl. Nachverfolgung). Verwenden, wenn WordCraft-Werkzeuge verbunden sind oder der Nutzer WordCraft nennt; typisch für Angebote, Briefe, Betriebsanleitungen, Arbeitsanweisungen, 8D-Reports, Vorlagen. Nicht für eingebaute docx-Hilfen ohne WordCraft-Server. | 0.3.0 | [README](skills/wordcraft-mcp/README.md) | [wordcraft-mcp-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-v0.3.0.zip) | [wordcraft-mcp-v0.3.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-v0.3.0.mcpb) | [wordcraft-mcp-cursor-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-cursor-v0.3.0.zip) | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=wordcraft&config=eyJ3b3JkY3JhZnQiOnsiYXJncyI6WyJtY3AiLCItLWF1dGhvciIsIkNsYXVkZSIsIi0tYXV0b21hdGlvbi1yZWFkLXJvb3QiLCIke3VzZXJIb21lfS9Eb2N1bWVudHMiLCItLWF1dG9tYXRpb24td3JpdGUtcm9vdCIsIiR7dXNlckhvbWV9L0RvY3VtZW50cy9DbGF1ZGUiXSwiY29tbWFuZCI6IkM6XFxQcm9ncmFtIEZpbGVzXFxXb3JkQ3JhZnRcXHdvcmRjcmFmdC1jbGkuZXhlIn19) | +| [gridcraft-mcp](skills/gridcraft-mcp/) | Excel-Arbeitsmappen (.xlsx, .csv) mit dem GridCraft-MCP-Server erstellen, öffnen, ändern, berechnen und prüfen. Verwenden, wenn GridCraft-Werkzeuge verbunden sind oder der Nutzer GridCraft nennt; typisch für Kalkulationen, Stücklisten (BOM), Listen, Diagramme, PDF-Export. Nicht für eingebaute xlsx-Hilfen ohne GridCraft-Server. | 0.3.0 | [README](skills/gridcraft-mcp/README.md) | [gridcraft-mcp-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-v0.3.0.zip) | [gridcraft-mcp-v0.3.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-v0.3.0.mcpb) | [gridcraft-mcp-cursor-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.3.0/gridcraft-mcp-cursor-v0.3.0.zip) | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=gridcraft&config=eyJjb21tYW5kIjoiQzovUHJvZ3JhbSBGaWxlcy9HcmlkQ3JhZnQvZ3JpZGNyYWZ0LWNsaS5leGUiLCJhcmdzIjpbIm1jcCJdfQ) | +| [wordcraft-mcp](skills/wordcraft-mcp/) | Word-Dokumente (.docx, .odt, .md, PDF) mit dem WordCraft-MCP-Server erstellen, bearbeiten und prüfen (inkl. Nachverfolgung). Verwenden, wenn WordCraft-Werkzeuge verbunden sind oder der Nutzer WordCraft nennt; typisch für Angebote, Briefe, Betriebsanleitungen, Arbeitsanweisungen, 8D-Reports, Vorlagen. Nicht für eingebaute docx-Hilfen ohne WordCraft-Server. | 0.3.0 | [README](skills/wordcraft-mcp/README.md) | [wordcraft-mcp-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-v0.3.0.zip) | [wordcraft-mcp-v0.3.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-v0.3.0.mcpb) | [wordcraft-mcp-cursor-v0.3.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.3.0/wordcraft-mcp-cursor-v0.3.0.zip) | [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=wordcraft&config=eyJjb21tYW5kIjoiQzovUHJvZ3JhbSBGaWxlcy9Xb3JkQ3JhZnQvd29yZGNyYWZ0LWNsaS5leGUiLCJhcmdzIjpbIm1jcCIsIi0tYXV0aG9yIiwiQ2xhdWRlIiwiLS1hdXRvbWF0aW9uLXJlYWQtcm9vdCIsIiR7dXNlckhvbWV9L0RvY3VtZW50cyIsIi0tYXV0b21hdGlvbi13cml0ZS1yb290IiwiJHt1c2VySG9tZX0vRG9jdW1lbnRzL0NsYXVkZSJdfQ) | ## Skill installieren (Claude) diff --git a/docs/CURSOR.md b/docs/CURSOR.md index 257fee0..eb1776c 100644 --- a/docs/CURSOR.md +++ b/docs/CURSOR.md @@ -56,6 +56,8 @@ GridCraft: Unterstützte Variablen in `mcp.json`: `${userHome}`, `${env:NAME}`, `${workspaceFolder}`, `${pathSeparator}` (siehe Cursor-MCP-Doku). In Plugin-`mcp.json` zusätzlich Plugin-Variablen (`${READ_ROOT}`, …) und `${CURSOR_PLUGIN_ROOT}` (hier unnötig, keine gebündelte EXE). +**1-Klick-Deeplink:** `config` ist nur das Transport-Objekt (`{command, args}`), der Servername steht in `name=`. Pfade mit Schrägstrich (`C:/Program Files/...`). Ein Wrapper `{ servername: {command…} }` im `config` führt in Cursor zu leerem `command`. + ## Test Im Agent: „Sind die WordCraft-Werkzeuge verfügbar?“ bzw. GridCraft. Kleines Testdokument im Schreib-Ordner anlegen. diff --git a/scripts/cursor-deeplink.ps1 b/scripts/cursor-deeplink.ps1 index 3a7384d..2aafd00 100644 --- a/scripts/cursor-deeplink.ps1 +++ b/scripts/cursor-deeplink.ps1 @@ -1,6 +1,8 @@ -# Builds a Cursor MCP install deeplink from a server name and config object (hashtable/JSON). -# Usage: . scripts\cursor-deeplink.ps1; New-CursorDeeplink -Name wordcraft -Config @{ command='...'; args=@(...) } -# Or: scripts\cursor-deeplink.ps1 -Name wordcraft -ConfigJson '{...}' +# Builds a Cursor MCP install deeplink. +# config = base64(JSON.stringify({command, args, ...})) -- transport only +# name = server key (separate query param) +# Wrapping as { serverName: transport } makes Cursor show command: "". +# Usage: . scripts\cursor-deeplink.ps1; New-CursorDeeplink -ServerName wordcraft -Config @{ command='...'; args=@('mcp') } param( [string]$Name, [string]$ConfigJson @@ -8,26 +10,58 @@ param( . (Join-Path $PSScriptRoot 'lib.ps1') function New-CursorDeeplink([string]$ServerName, $Config) { - # Config = server transport object (command/args/...) OR { serverName = transport }. - # Deeplink uses name= + base64(JSON of the transport object keyed by name), per Cursor docs. + $command = $null + $argList = @() + $extra = @{} + if ($Config -is [string]) { - $obj = $Config | ConvertFrom-Json + $Config = $Config | ConvertFrom-Json + } + + if ($Config -is [hashtable]) { + if ($Config.ContainsKey($ServerName) -and $Config[$ServerName] -is [hashtable]) { + $Config = $Config[$ServerName] + } + $command = [string]$Config['command'] + if ($Config.ContainsKey('args')) { $argList = @($Config['args']) } + foreach ($k in $Config.Keys) { + if ($k -ne 'command' -and $k -ne 'args') { $extra[$k] = $Config[$k] } + } } else { - $obj = $Config + # PSCustomObject + $props = @($Config.PSObject.Properties.Name) + if ($props.Count -eq 1 -and $props[0] -eq $ServerName) { + $Config = $Config.$ServerName + $props = @($Config.PSObject.Properties.Name) + } + $command = [string]$Config.command + if ($null -ne $Config.args) { $argList = @($Config.args) } + foreach ($p in $Config.PSObject.Properties) { + if ($p.Name -ne 'command' -and $p.Name -ne 'args') { $extra[$p.Name] = $p.Value } + } } - # Normalize to { name: transport } - $props = @($obj.PSObject.Properties.Name) - if ($props.Count -eq 1 -and $props[0] -eq $ServerName) { - $payload = $obj + + # Forward slashes: valid on Windows, safer through JSON/URL pipelines. + if ($command -match '^[A-Za-z]:\\') { + $command = $command -replace '\\', '/' } - elseif ($obj.command -or $obj.PSObject.Properties['command']) { - $payload = @{ $ServerName = $obj } + + $transport = [ordered]@{ command = $command; args = @($argList) } + foreach ($k in $extra.Keys) { $transport[$k] = $extra[$k] } + + $jsonPath = [IO.Path]::GetTempFileName() + try { + # Write via .NET so Python can re-dump with JSON.stringify-compatible compact form. + $psJson = ($transport | ConvertTo-Json -Compress -Depth 20) + [IO.File]::WriteAllText($jsonPath, $psJson, $Utf8NoBom) + $json = & python -c "import json,sys; print(json.dumps(json.load(open(sys.argv[1],encoding='utf-8')),separators=(',',':'),ensure_ascii=False))" $jsonPath + if (-not $json) { $json = $psJson } } - else { - $payload = $obj + finally { + Remove-Item -LiteralPath $jsonPath -Force -ErrorAction SilentlyContinue } - $json = ($payload | ConvertTo-Json -Compress -Depth 20) + $bytes = $Utf8NoBom.GetBytes($json) $b64 = [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_') return "cursor://anysphere.cursor-deeplink/mcp/install?name=$([Uri]::EscapeDataString($ServerName))&config=$b64" diff --git a/scripts/cursor-deeplink.sh b/scripts/cursor-deeplink.sh index 224c90b..40ef0f0 100644 --- a/scripts/cursor-deeplink.sh +++ b/scripts/cursor-deeplink.sh @@ -13,7 +13,14 @@ python3 -c ' import base64, json, sys, urllib.parse name, raw = sys.argv[1], sys.argv[2] transport = json.loads(raw) -payload = {name: transport} -b64 = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode("utf-8")).decode("ascii").rstrip("=") +# Unwrap { name: transport } if the caller passed it that way. +if set(transport.keys()) == {name} and isinstance(transport[name], dict): + transport = transport[name] +cmd = transport.get("command") or "" +if len(cmd) >= 3 and cmd[1] == ":" and "\\" in cmd: + transport = dict(transport) + transport["command"] = cmd.replace("\\\\", "/").replace("\\", "/") +# config = transport only; name= is a separate query param. +b64 = base64.urlsafe_b64encode(json.dumps(transport, separators=(",", ":")).encode("utf-8")).decode("ascii").rstrip("=") print(f"cursor://anysphere.cursor-deeplink/mcp/install?name={urllib.parse.quote(name)}&config={b64}") ' "$name" "$config" diff --git a/scripts/update-index.sh b/scripts/update-index.sh index 75dcc7f..2630d25 100755 --- a/scripts/update-index.sh +++ b/scripts/update-index.sh @@ -56,8 +56,11 @@ for a in man["server"]["mcp_config"].get("args") or []: args.append("${userHome}/Documents/Claude") else: args.append(s) -payload = {sk: {"command": cmd, "args": args}} -b64 = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).decode().rstrip("=") +# Forward slashes; config = transport only (name= is separate). +if len(cmd) >= 3 and cmd[1] == ":" and "\\" in cmd: + cmd = cmd.replace("\\", "/") +transport = {"command": cmd, "args": args} +b64 = base64.urlsafe_b64encode(json.dumps(transport, separators=(",", ":")).encode()).decode().rstrip("=") link = f"cursor://anysphere.cursor-deeplink/mcp/install?name={urllib.parse.quote(sk)}&config={b64}" print(f"[Add to Cursor]({link})") PY