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
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ npm run start
- **`SDE_HOST`**: `https://your-sdelements-instance.com`
- **`SDE_API_KEY`**: `your-api-key-here`

### HTTP setup

Use HTTP mode when you want a long-running server instead of STDIO.

```bash
npm run start:http
```

- **Credentials**: do **not** set `SDE_HOST` or `SDE_API_KEY` in the server process. The HTTP server refuses to start if either is set (including `SDE_API_KEY`). Each client request must provide credentials instead.
- **Instance allowlist (required)**: set `MCP_SDE_INSTANCE_ALLOWLIST` to a comma-separated list of allowed SDE hosts (for example `https://sde.example.com,https://sde2.example.com`). Requests with `SDE_HOST` outside this list are rejected.
- **Port/host**: configure with `MCP_PORT` (default `3000`) and `MCP_HOST` (default `127.0.0.1`).
- **Per-request auth**: send `SDE_HOST` and `SDE_API_KEY` as headers (or `sde_host` / `sde_api_key` in the initialize request body).

### HTTPS unsafe mode

By default, the server rejects non-HTTPS `SDE_HOST` values. For local/dev instances that only serve HTTP, set:

```bash
SDE_ALLOW_INSECURE_HTTP=true
```

This allows `http://` hosts. Use only in trusted environments.

Example initialize request:

```bash
curl -sS http://127.0.0.1:3000/mcp \
-H "Content-Type: application/json" \
-H "SDE_HOST: https://your-sdelements-instance.com" \
-H "SDE_API_KEY: your-api-key-here" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"example","version":"0.0.0"}}}'
```

### Client setup (Cursor + Claude Desktop)

Both clients use the same `mcpServers` object — the only difference is **where you paste it**.
Expand Down Expand Up @@ -60,6 +93,24 @@ Pick one execution style:
}
```

### HTTP client setup (Cursor)

Start the HTTP server (`npm run start:http`) and set `MCP_SDE_INSTANCE_ALLOWLIST` as described above. Then configure Cursor with an HTTP MCP server entry:

```json
{
"mcpServers": {
"sdelements-http": {
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"SDE_HOST": "https://your-sdelements-instance.com",
"SDE_API_KEY": "your-api-key-here"
}
}
}
}
```

### Build

```bash
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
"mcpName": "io.github.sdelements/sde-mcp",
"scripts": {
"start": "npm run build && node dist/index.js",
"start:http": "npm run build && node dist/index.js --http",
"start:stdio": "npm run build && node dist/index.js",
"prepare": "npm run build",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest --watch",
"lint": "eslint",
"clean": "rm -rf dist",
"build": "tsc --project tsconfig.build.json",
"build:mcpb": "./build-mcpb.sh",
"build": "npm run clean && tsc --project tsconfig.build.json --emitDeclarationOnly && node scripts/build.mjs",
"build:mcpb": "./scripts/build-mcpb.sh",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
Expand All @@ -43,5 +45,8 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.49.0",
"vitest": "^4.0.15"
},
"engines": {
"node": "18.x"
}
}
File renamed without changes.
58 changes: 58 additions & 0 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { build } from "esbuild";
import fs from "node:fs";
import path from "node:path";

const repoRoot = path.resolve(
path.dirname(new URL(import.meta.url).pathname),

Check failure on line 6 in scripts/build.mjs

View workflow job for this annotation

GitHub Actions / ci (lts/*)

'URL' is not defined

Check failure on line 6 in scripts/build.mjs

View workflow job for this annotation

GitHub Actions / ci (current)

'URL' is not defined

Check failure on line 6 in scripts/build.mjs

View workflow job for this annotation

GitHub Actions / ci (lts/*)

'URL' is not defined
".."
);
const srcDir = path.join(repoRoot, "src");
const distDir = path.join(repoRoot, "dist");

function collectTsFiles(dir) {
/** @type {string[]} */
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.name.startsWith(".")) continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...collectTsFiles(fullPath));
continue;
}
if (!entry.isFile()) continue;
if (entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")) {
results.push(fullPath);
}
}
return results;
}

const entryPoints = collectTsFiles(srcDir);

await build({
entryPoints,
outdir: distDir,
outbase: srcDir,
bundle: true,
splitting: true,
format: "esm",
platform: "node",
target: "node20",
packages: "external",
sourcemap: false,
legalComments: "none",
});

// Ensure CLI entry has a proper shebang at the very top (esbuild may drop it when bundling).
const distIndexPath = path.join(distDir, "index.js");
if (fs.existsSync(distIndexPath)) {
const raw = fs.readFileSync(distIndexPath, "utf8");
const shebang = "#!/usr/bin/env node\n";
if (!raw.startsWith("#!")) {
fs.writeFileSync(distIndexPath, shebang + raw, "utf8");
} else if (!raw.startsWith(shebang)) {
// Normalize to our expected shebang.
const withoutFirstLine = raw.replace(/^#!.*\n/, "");
fs.writeFileSync(distIndexPath, shebang + withoutFirstLine, "utf8");
}
}
Loading
Loading