Add plugin builder for one-file client install
plugin/make-plugin.{sh,ps1} stamp a user's own Freshservice API key and the
shared server token into a template and emit a personalized freshservice.plugin
(direct HTTP MCP connection — no Node/mcp-remote/config editing on clients).
Built plugins embed credentials, so *.plugin is git-ignored; the committed
template holds only placeholders. Validated with 'claude plugin validate'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,3 +6,7 @@ __pycache__/
|
|||||||
|
|
||||||
# MCP server runtime secrets
|
# MCP server runtime secrets
|
||||||
mcp-server/.env
|
mcp-server/.env
|
||||||
|
|
||||||
|
# Personalized plugin builds contain API keys — never commit
|
||||||
|
*.plugin
|
||||||
|
!freshservice-skill.zip
|
||||||
|
|||||||
46
plugin/README.md
Normal file
46
plugin/README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Freshservice plugin builder
|
||||||
|
|
||||||
|
Generates a personalized **`freshservice.plugin`** that connects Claude
|
||||||
|
Desktop / Cowork to the shared Freshservice MCP server on the LAN — no Node.js,
|
||||||
|
no `mcp-remote`, no hand-editing `claude_desktop_config.json`. The plugin uses
|
||||||
|
a direct HTTP MCP connection with your own identity headers, so everything you
|
||||||
|
do in Freshservice is attributed to **you**.
|
||||||
|
|
||||||
|
## For coworkers: get connected in three steps
|
||||||
|
|
||||||
|
1. **Get your Freshservice API key**: Freshservice → profile picture →
|
||||||
|
*Profile settings* → API key. Also ask IT (Spencer) for the **shared server
|
||||||
|
token**.
|
||||||
|
|
||||||
|
2. **Build your plugin** (from a clone of this repo):
|
||||||
|
|
||||||
|
macOS / Linux:
|
||||||
|
```bash
|
||||||
|
cd plugin && ./make-plugin.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows (PowerShell):
|
||||||
|
```powershell
|
||||||
|
cd plugin
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\make-plugin.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter your API key and the shared token when prompted. This produces
|
||||||
|
`freshservice.plugin` **containing your key — don't share the file.**
|
||||||
|
|
||||||
|
3. **Install it**: in Claude Desktop, go to Cowork → **Customize** → add the
|
||||||
|
`.plugin` file. Start a new chat; the Freshservice tools are available.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Agent account in the PESCO Freshservice instance
|
||||||
|
- Network reach to `192.168.101.12:3839` (on-site or VPN)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Defaults (server URL, domain) can be overridden via env vars:
|
||||||
|
`SERVER_URL=... FS_DOMAIN=... ./make-plugin.sh`
|
||||||
|
- Built `.plugin` files are git-ignored on purpose — they embed credentials.
|
||||||
|
- The server side of this lives in [`../mcp-server/`](../mcp-server/); it must
|
||||||
|
be running in multi-user mode (no `FRESHSERVICE_*` env baked in) for
|
||||||
|
per-user identity to be enforced.
|
||||||
49
plugin/make-plugin.ps1
Normal file
49
plugin/make-plugin.ps1
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Build a personalized freshservice.plugin for Claude Desktop / Cowork (Windows).
|
||||||
|
#
|
||||||
|
# Each person runs this ONCE with their own Freshservice API key. The output
|
||||||
|
# .plugin file contains your key — do not share it.
|
||||||
|
#
|
||||||
|
# Usage: powershell -ExecutionPolicy Bypass -File .\make-plugin.ps1
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$Template = Join-Path $ScriptDir "template"
|
||||||
|
|
||||||
|
$ServerUrl = if ($env:SERVER_URL) { $env:SERVER_URL } else { "http://192.168.101.12:3839/mcp" }
|
||||||
|
$FsDomain = if ($env:FS_DOMAIN) { $env:FS_DOMAIN } else { "pesco.freshservice.com" }
|
||||||
|
|
||||||
|
$FsKey = if ($env:FS_KEY) { $env:FS_KEY } else {
|
||||||
|
Read-Host "Your Freshservice API key (Freshservice -> profile picture -> Profile settings -> API key)"
|
||||||
|
}
|
||||||
|
$McpToken = if ($env:MCP_TOKEN) { $env:MCP_TOKEN } else {
|
||||||
|
Read-Host "Shared MCP server token (ask IT / Spencer)"
|
||||||
|
}
|
||||||
|
if (-not $FsKey) { throw "API key is required." }
|
||||||
|
if (-not $McpToken) { throw "MCP token is required." }
|
||||||
|
|
||||||
|
$Build = Join-Path ([System.IO.Path]::GetTempPath()) ("fsplugin-" + [guid]::NewGuid())
|
||||||
|
Copy-Item -Recurse -Force $Template $Build
|
||||||
|
|
||||||
|
$McpJson = Join-Path $Build ".mcp.json"
|
||||||
|
$content = Get-Content -Raw $McpJson
|
||||||
|
$content = $content.Replace("__SERVER_URL__", $ServerUrl)
|
||||||
|
$content = $content.Replace("__FS_DOMAIN__", $FsDomain)
|
||||||
|
$content = $content.Replace("__FS_KEY__", $FsKey)
|
||||||
|
$content = $content.Replace("__MCP_TOKEN__", $McpToken)
|
||||||
|
Set-Content -NoNewline -Path $McpJson -Value $content
|
||||||
|
|
||||||
|
# Sanity check: no placeholders left anywhere.
|
||||||
|
$leftover = Get-ChildItem -Recurse -File $Build | Select-String -Pattern "__[A-Z_]*__" -SimpleMatch:$false
|
||||||
|
if ($leftover) { throw "Unfilled placeholders remain; aborting." }
|
||||||
|
|
||||||
|
$Out = Join-Path $ScriptDir "freshservice.plugin"
|
||||||
|
if (Test-Path $Out) { Remove-Item $Out }
|
||||||
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||||
|
# CreateFromDirectory includes dot-directories (.claude-plugin), unlike
|
||||||
|
# Compress-Archive with a wildcard path.
|
||||||
|
[System.IO.Compression.ZipFile]::CreateFromDirectory($Build, $Out)
|
||||||
|
Remove-Item -Recurse -Force $Build
|
||||||
|
|
||||||
|
Write-Host "Built: $Out"
|
||||||
|
Write-Host "Install it in Claude Desktop (Cowork -> Customize -> add plugin), then start a new chat."
|
||||||
|
Write-Host "REMINDER: this file contains your personal API key -- don't share it."
|
||||||
55
plugin/make-plugin.sh
Executable file
55
plugin/make-plugin.sh
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build a personalized freshservice.plugin for Claude Desktop / Cowork.
|
||||||
|
#
|
||||||
|
# Each person runs this ONCE with their own Freshservice API key. The output
|
||||||
|
# .plugin file contains your key — do not share it.
|
||||||
|
#
|
||||||
|
# Usage (interactive):
|
||||||
|
# ./make-plugin.sh
|
||||||
|
# Or non-interactive:
|
||||||
|
# FS_KEY=... MCP_TOKEN=... ./make-plugin.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TEMPLATE="$SCRIPT_DIR/template"
|
||||||
|
|
||||||
|
SERVER_URL="${SERVER_URL:-http://192.168.101.12:3839/mcp}"
|
||||||
|
FS_DOMAIN="${FS_DOMAIN:-pesco.freshservice.com}"
|
||||||
|
|
||||||
|
if [ -z "${FS_KEY:-}" ]; then
|
||||||
|
printf "Your Freshservice API key (Freshservice → profile picture → Profile settings → API key): "
|
||||||
|
read -r FS_KEY
|
||||||
|
fi
|
||||||
|
if [ -z "${MCP_TOKEN:-}" ]; then
|
||||||
|
printf "Shared MCP server token (ask IT / Spencer): "
|
||||||
|
read -r MCP_TOKEN
|
||||||
|
fi
|
||||||
|
[ -n "$FS_KEY" ] || { echo "ERROR: API key is required." >&2; exit 1; }
|
||||||
|
[ -n "$MCP_TOKEN" ] || { echo "ERROR: MCP token is required." >&2; exit 1; }
|
||||||
|
|
||||||
|
BUILD="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$BUILD"' EXIT
|
||||||
|
cp -R "$TEMPLATE/." "$BUILD/"
|
||||||
|
|
||||||
|
# Stamp placeholders using bash substitution (safe for special characters).
|
||||||
|
for f in "$BUILD/.mcp.json"; do
|
||||||
|
content="$(cat "$f")"
|
||||||
|
content="${content//__SERVER_URL__/$SERVER_URL}"
|
||||||
|
content="${content//__FS_DOMAIN__/$FS_DOMAIN}"
|
||||||
|
content="${content//__FS_KEY__/$FS_KEY}"
|
||||||
|
content="${content//__MCP_TOKEN__/$MCP_TOKEN}"
|
||||||
|
printf '%s' "$content" > "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Sanity check: no placeholders left anywhere.
|
||||||
|
if grep -rq "__[A-Z_]*__" "$BUILD"; then
|
||||||
|
echo "ERROR: unfilled placeholders remain; aborting." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUT="$SCRIPT_DIR/freshservice.plugin"
|
||||||
|
rm -f "$OUT"
|
||||||
|
(cd "$BUILD" && zip -r -q "$OUT" . -x "*.DS_Store")
|
||||||
|
echo "Built: $OUT"
|
||||||
|
echo "Install it in Claude Desktop (Cowork → Customize → add plugin), then start a new chat."
|
||||||
|
echo "REMINDER: this file contains your personal API key — don't share it."
|
||||||
8
plugin/template/.claude-plugin/plugin.json
Normal file
8
plugin/template/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "freshservice",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Connect Claude to the PESCO Freshservice service desk — tickets, assets/CMDB, people, and changes/problems/releases — through the shared LAN MCP server, acting as your own agent identity.",
|
||||||
|
"author": {
|
||||||
|
"name": "Spencer McGuire"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
plugin/template/.mcp.json
Normal file
13
plugin/template/.mcp.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"freshservice": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "__SERVER_URL__",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer __MCP_TOKEN__",
|
||||||
|
"X-Freshservice-Domain": "__FS_DOMAIN__",
|
||||||
|
"X-Freshservice-Key": "__FS_KEY__"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
plugin/template/README.md
Normal file
20
plugin/template/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Freshservice plugin
|
||||||
|
|
||||||
|
Connects Claude (Desktop / Cowork) to the PESCO Freshservice service desk
|
||||||
|
through the shared MCP server on the LAN. Tickets, assets/CMDB, people, and
|
||||||
|
change/problem/release records — with every action attributed to **your own**
|
||||||
|
Freshservice agent account.
|
||||||
|
|
||||||
|
## This file is personalized
|
||||||
|
|
||||||
|
This plugin was generated by `make-plugin` and contains **your** Freshservice
|
||||||
|
API key and the shared server token. Treat it like a password:
|
||||||
|
|
||||||
|
- Do **not** share your `.plugin` file with anyone else — they would act as you.
|
||||||
|
- If your key is exposed, regenerate it in Freshservice (Profile settings →
|
||||||
|
API key) and rebuild the plugin.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- You must be an agent in the PESCO Freshservice instance.
|
||||||
|
- Your computer must be able to reach the LAN server (on-site network or VPN).
|
||||||
Reference in New Issue
Block a user