@qune-tech/ocds-mcp 0.3.0
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.
- package/README.md +104 -0
- package/index.js +30 -0
- package/package.json +33 -0
- package/postinstall.js +149 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.qune-tech/ocds-mcp -->
|
|
2
|
+
|
|
3
|
+
# @qune-tech/ocds-mcp
|
|
4
|
+
|
|
5
|
+
MCP server for German public procurement data (OCDS). Connects your AI assistant to the [Vergabe Dashboard](https://vergabe-dashboard.qune.de) API for semantic search, tender matching, and company profile management.
|
|
6
|
+
|
|
7
|
+
Company profiles never leave your machine. GDPR-compliant by design.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @qune-tech/ocds-mcp --api-key sk_live_YOUR_KEY_HERE
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Get an API key
|
|
16
|
+
|
|
17
|
+
Sign up at [vergabe-dashboard.qune.de](https://vergabe-dashboard.qune.de) and create an API key (MCP or Enterprise plan required).
|
|
18
|
+
|
|
19
|
+
## Configure your AI client
|
|
20
|
+
|
|
21
|
+
### Claude Desktop
|
|
22
|
+
|
|
23
|
+
Edit `claude_desktop_config.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"ocds": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@qune-tech/ocds-mcp", "--api-key", "sk_live_YOUR_KEY_HERE"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Claude Code
|
|
37
|
+
|
|
38
|
+
Add `.mcp.json` to your project root:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"ocds": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "@qune-tech/ocds-mcp", "--api-key", "sk_live_YOUR_KEY_HERE"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Cursor
|
|
52
|
+
|
|
53
|
+
Settings > MCP Servers > Add:
|
|
54
|
+
|
|
55
|
+
- Command: `npx`
|
|
56
|
+
- Args: `-y @qune-tech/ocds-mcp --api-key sk_live_YOUR_KEY_HERE`
|
|
57
|
+
|
|
58
|
+
## Available Tools
|
|
59
|
+
|
|
60
|
+
| Tool | Description |
|
|
61
|
+
|------|-------------|
|
|
62
|
+
| `search_text` | Semantic search across all tenders |
|
|
63
|
+
| `list_releases` | Filter and browse tenders by month, CPV code, category, value range |
|
|
64
|
+
| `get_release` | Full tender details by OCID |
|
|
65
|
+
| `get_index_info` | Database statistics and connectivity check |
|
|
66
|
+
| `create_company_profile` | Create a matching profile for your company |
|
|
67
|
+
| `update_company_profile` | Update an existing profile |
|
|
68
|
+
| `get_company_profile` | View profile details |
|
|
69
|
+
| `list_company_profiles` | List all your profiles |
|
|
70
|
+
| `delete_company_profile` | Delete a profile |
|
|
71
|
+
| `match_tenders` | Match a profile against all tenders with semantic similarity |
|
|
72
|
+
|
|
73
|
+
## How It Works
|
|
74
|
+
|
|
75
|
+
The npm package downloads the correct platform-native binary on install. No Node.js runtime dependency for the actual MCP server.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
LLM <--stdio--> ocds-mcp (local binary)
|
|
79
|
+
| Local: company profiles + sentence embedder
|
|
80
|
+
| Remote: searches, release queries
|
|
81
|
+
+--HTTPS--> Vergabe Dashboard API
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Company profiles are stored locally (never leave your machine).
|
|
85
|
+
- Text embeddings are computed locally (multilingual-e5-small ONNX model, ~118 MB, auto-downloaded on first run).
|
|
86
|
+
- Only embedding vectors are sent to the API for search and matching.
|
|
87
|
+
|
|
88
|
+
## Supported Platforms
|
|
89
|
+
|
|
90
|
+
| Platform | Architecture |
|
|
91
|
+
|----------|-------------|
|
|
92
|
+
| Linux | x86_64 |
|
|
93
|
+
| macOS | Apple Silicon (ARM64) |
|
|
94
|
+
| Windows | x86_64 |
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
|
|
98
|
+
- An API key from [vergabe-dashboard.qune.de](https://vergabe-dashboard.qune.de)
|
|
99
|
+
- ~200 MB disk space for the ONNX model (auto-downloaded on first run)
|
|
100
|
+
- Internet connection to reach the API
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
const binary = process.platform === "win32" ? "ocds-mcp.exe" : "ocds-mcp";
|
|
9
|
+
const binaryPath = path.join(__dirname, "bin", binary);
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(binaryPath)) {
|
|
12
|
+
console.error(
|
|
13
|
+
`ocds-mcp binary not found at ${binaryPath}\n` +
|
|
14
|
+
`Run "npm rebuild @qune-tech/ocds-mcp" to trigger the download.`
|
|
15
|
+
);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
20
|
+
stdio: "inherit",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on("error", (err) => {
|
|
24
|
+
console.error(`Failed to start ocds-mcp: ${err.message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on("exit", (code) => {
|
|
29
|
+
process.exit(code ?? 1);
|
|
30
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qune-tech/ocds-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for German public procurement data (OCDS). Semantic search, tender matching, and company profile management.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/qune-tech/ocds-mcp"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://vergabe-dashboard.qune.de",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"ocds",
|
|
14
|
+
"procurement",
|
|
15
|
+
"tenders",
|
|
16
|
+
"vergabe"
|
|
17
|
+
],
|
|
18
|
+
"mcpName": "io.github.qune-tech/ocds-mcp",
|
|
19
|
+
"bin": {
|
|
20
|
+
"ocds-mcp": "index.js"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"postinstall": "node postinstall.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"index.js",
|
|
27
|
+
"postinstall.js",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=16"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
|
|
10
|
+
const VERSION = require("./package.json").version;
|
|
11
|
+
const REPO = "qune-tech/ocds-mcp";
|
|
12
|
+
const BASE_URL = `https://github.com/${REPO}/releases/download/v${VERSION}`;
|
|
13
|
+
|
|
14
|
+
const PLATFORMS = {
|
|
15
|
+
"linux-x64": {
|
|
16
|
+
asset: "ocds-mcp-linux-x86_64.tar.gz",
|
|
17
|
+
extracted: "ocds-mcp-linux-x86_64",
|
|
18
|
+
binary: "ocds-mcp",
|
|
19
|
+
},
|
|
20
|
+
"darwin-arm64": {
|
|
21
|
+
asset: "ocds-mcp-macos-arm64.tar.gz",
|
|
22
|
+
extracted: "ocds-mcp-macos-arm64",
|
|
23
|
+
binary: "ocds-mcp",
|
|
24
|
+
},
|
|
25
|
+
"win32-x64": {
|
|
26
|
+
asset: "ocds-mcp-windows-x86_64.zip",
|
|
27
|
+
extracted: "ocds-mcp-windows-x86_64.exe",
|
|
28
|
+
binary: "ocds-mcp.exe",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function getPlatformKey() {
|
|
33
|
+
return `${process.platform}-${process.arch}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function download(url) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const request = (url) => {
|
|
39
|
+
https
|
|
40
|
+
.get(url, (res) => {
|
|
41
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
42
|
+
request(res.headers.location);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (res.statusCode !== 200) {
|
|
46
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode} for ${url}`));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const chunks = [];
|
|
50
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
51
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
52
|
+
res.on("error", reject);
|
|
53
|
+
})
|
|
54
|
+
.on("error", reject);
|
|
55
|
+
};
|
|
56
|
+
request(url);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractTarGz(buffer, destDir) {
|
|
61
|
+
const archivePath = path.join(destDir, "archive.tar.gz");
|
|
62
|
+
fs.writeFileSync(archivePath, buffer);
|
|
63
|
+
execSync(`tar xzf "${archivePath}" -C "${destDir}"`, { stdio: "ignore" });
|
|
64
|
+
fs.unlinkSync(archivePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractZip(buffer, destDir) {
|
|
68
|
+
const archivePath = path.join(destDir, "archive.zip");
|
|
69
|
+
fs.writeFileSync(archivePath, buffer);
|
|
70
|
+
// Use PowerShell on Windows, unzip on Unix
|
|
71
|
+
if (process.platform === "win32") {
|
|
72
|
+
execSync(
|
|
73
|
+
`powershell -Command "Expand-Archive -Force '${archivePath}' '${destDir}'"`,
|
|
74
|
+
{ stdio: "ignore" }
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
execSync(`unzip -o "${archivePath}" -d "${destDir}"`, { stdio: "ignore" });
|
|
78
|
+
}
|
|
79
|
+
fs.unlinkSync(archivePath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
const key = getPlatformKey();
|
|
84
|
+
const info = PLATFORMS[key];
|
|
85
|
+
|
|
86
|
+
if (!info) {
|
|
87
|
+
console.error(
|
|
88
|
+
`Unsupported platform: ${key}\n` +
|
|
89
|
+
`Supported: ${Object.keys(PLATFORMS).join(", ")}\n` +
|
|
90
|
+
`You can build from source: https://github.com/${REPO}#building-from-source`
|
|
91
|
+
);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const binDir = path.join(__dirname, "bin");
|
|
96
|
+
const binaryPath = path.join(binDir, info.binary);
|
|
97
|
+
|
|
98
|
+
// Skip download if binary already exists (cached)
|
|
99
|
+
if (fs.existsSync(binaryPath)) {
|
|
100
|
+
console.log(`ocds-mcp binary already exists at ${binaryPath}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const url = `${BASE_URL}/${info.asset}`;
|
|
105
|
+
console.log(`Downloading ocds-mcp v${VERSION} for ${key}...`);
|
|
106
|
+
console.log(` ${url}`);
|
|
107
|
+
|
|
108
|
+
let buffer;
|
|
109
|
+
try {
|
|
110
|
+
buffer = await download(url);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(`Failed to download ocds-mcp binary: ${err.message}`);
|
|
113
|
+
console.error(
|
|
114
|
+
`\nYou can download it manually from:\n https://github.com/${REPO}/releases/tag/v${VERSION}`
|
|
115
|
+
);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
if (info.asset.endsWith(".tar.gz")) {
|
|
122
|
+
extractTarGz(buffer, binDir);
|
|
123
|
+
} else if (info.asset.endsWith(".zip")) {
|
|
124
|
+
extractZip(buffer, binDir);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Rename extracted binary to canonical name
|
|
128
|
+
const extractedPath = path.join(binDir, info.extracted);
|
|
129
|
+
if (fs.existsSync(extractedPath) && info.extracted !== info.binary) {
|
|
130
|
+
fs.renameSync(extractedPath, binaryPath);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Make binary executable on Unix
|
|
134
|
+
if (process.platform !== "win32") {
|
|
135
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(binaryPath)) {
|
|
139
|
+
console.error(
|
|
140
|
+
`Binary not found after extraction. Expected: ${binaryPath}\n` +
|
|
141
|
+
`Please report this issue at https://github.com/${REPO}/issues`
|
|
142
|
+
);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`ocds-mcp v${VERSION} installed to ${binaryPath}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
main();
|