@clavisagent/mcp-server 0.1.0 → 0.1.1
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 +63 -26
- package/dist/index.d.ts +3 -3
- package/dist/index.js +243 -107
- package/dist/index.js.map +1 -1
- package/package.json +18 -34
- package/src/index.ts +342 -0
- package/tsconfig.json +17 -0
package/README.md
CHANGED
|
@@ -1,54 +1,91 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @clavisagent/mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for [Clavis](https://clavisagent.com) — secure credential management for Claude Desktop.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- 🔐 Encrypted credential storage (AES-256)
|
|
8
|
-
- 🔄 Automatic OAuth token refresh
|
|
9
|
-
- ⚡ Distributed rate limiting
|
|
10
|
-
- 📊 Full audit logging
|
|
5
|
+
Credentials are injected server-side. **Claude never sees raw API keys.**
|
|
11
6
|
|
|
12
7
|
## Installation
|
|
13
8
|
|
|
14
|
-
```bash
|
|
15
|
-
npx @clavisagent/mcp-server
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Or install globally:
|
|
19
|
-
|
|
20
9
|
```bash
|
|
21
10
|
npm install -g @clavisagent/mcp-server
|
|
22
11
|
```
|
|
23
12
|
|
|
24
|
-
##
|
|
13
|
+
## Claude Desktop Setup
|
|
25
14
|
|
|
26
|
-
Add
|
|
15
|
+
Add to your `claude_desktop_config.json`:
|
|
27
16
|
|
|
28
17
|
```json
|
|
29
18
|
{
|
|
30
19
|
"mcpServers": {
|
|
31
20
|
"clavis": {
|
|
32
21
|
"command": "npx",
|
|
33
|
-
"args": ["-y", "@clavisagent/mcp-server"]
|
|
22
|
+
"args": ["-y", "@clavisagent/mcp-server"],
|
|
23
|
+
"env": {
|
|
24
|
+
"CLAVIS_API_KEY": "eyJ...",
|
|
25
|
+
"CLAVIS_API_URL": "https://your-clavis-instance.com"
|
|
26
|
+
}
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
29
|
}
|
|
37
30
|
```
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
Config file locations:
|
|
33
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
34
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
Get your API key from `POST /v1/auth/login` — use the `access_token` (JWT), not the `cla_...` key shown at registration.
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
### `list_services`
|
|
41
|
+
List all services with stored credentials.
|
|
42
|
+
|
|
43
|
+
### `check_credential_status`
|
|
44
|
+
Check credential health without making any external API call.
|
|
45
|
+
```
|
|
46
|
+
service_name: "stripe"
|
|
47
|
+
→ Valid: yes, Status: healthy, Expires in: 58 minutes
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `call_service` ← Recommended
|
|
51
|
+
Make an API call with server-side credential injection. The credential never reaches Claude.
|
|
52
|
+
```
|
|
53
|
+
service_name: "stripe"
|
|
54
|
+
method: "GET"
|
|
55
|
+
url: "https://api.stripe.com/v1/balance"
|
|
56
|
+
→ Status: 200, Body: {"object": "balance", ...}
|
|
43
57
|
```
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
### `get_credentials` ← Legacy
|
|
60
|
+
Returns raw credential data. Use `call_service` instead — it keeps secrets out of the conversation entirely.
|
|
61
|
+
|
|
62
|
+
## Self-Hosted Setup
|
|
63
|
+
|
|
64
|
+
If running Clavis locally:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"clavis": {
|
|
70
|
+
"command": "node",
|
|
71
|
+
"args": ["/path/to/clavis-mcp/dist/index.js"],
|
|
72
|
+
"env": {
|
|
73
|
+
"CLAVIS_API_KEY": "eyJ...",
|
|
74
|
+
"CLAVIS_API_URL": "http://localhost:8000"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Changelog
|
|
82
|
+
|
|
83
|
+
### v0.1.1
|
|
84
|
+
- Added `call_service` tool (server-side credential injection)
|
|
85
|
+
- `call_service` is the recommended tool for prompt injection immunity
|
|
46
86
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| `get_credentials` | Retrieve a valid access token or API key for a named service |
|
|
50
|
-
| `list_services` | List all services with stored credentials |
|
|
51
|
-
| `check_credential_status` | Check the status and expiry of credentials for a service |
|
|
87
|
+
### v0.1.0
|
|
88
|
+
- Initial release: `list_services`, `check_credential_status`, `get_credentials`
|
|
52
89
|
|
|
53
90
|
## License
|
|
54
91
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Clavis MCP Server
|
|
4
4
|
*
|
|
5
|
-
* Exposes Clavis credential management to Claude Desktop
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Exposes Clavis credential management to Claude Desktop via the
|
|
6
|
+
* Model Context Protocol. Credentials are injected server-side —
|
|
7
|
+
* Claude never sees raw API keys.
|
|
8
8
|
*/
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,138 +1,274 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
2
3
|
/**
|
|
3
4
|
* Clavis MCP Server
|
|
4
5
|
*
|
|
5
|
-
* Exposes Clavis credential management to Claude Desktop
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Exposes Clavis credential management to Claude Desktop via the
|
|
7
|
+
* Model Context Protocol. Credentials are injected server-side —
|
|
8
|
+
* Claude never sees raw API keys.
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
12
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
13
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
|
-
// Config
|
|
15
|
+
// Config from environment
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
-
|
|
19
|
-
process.exit(1);
|
|
17
|
+
const CLAVIS_API_URL = (process.env.CLAVIS_API_URL ?? "http://localhost:8000").replace(/\/$/, "");
|
|
18
|
+
const CLAVIS_API_KEY = process.env.CLAVIS_API_KEY ?? "";
|
|
19
|
+
if (!CLAVIS_API_KEY) {
|
|
20
|
+
process.stderr.write("[clavis-mcp] WARNING: CLAVIS_API_KEY is not set. All requests will fail with 401.\n");
|
|
20
21
|
}
|
|
21
|
-
const
|
|
22
|
-
Authorization: `Bearer ${
|
|
22
|
+
const AUTH_HEADERS = {
|
|
23
|
+
Authorization: `Bearer ${CLAVIS_API_KEY}`,
|
|
23
24
|
"Content-Type": "application/json",
|
|
24
25
|
};
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Clavis API helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
25
29
|
async function clavisGet(path) {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
30
|
+
const { default: fetch } = await import("node-fetch");
|
|
31
|
+
const url = `${CLAVIS_API_URL}${path}`;
|
|
32
|
+
const res = await fetch(url, { headers: AUTH_HEADERS });
|
|
33
|
+
const body = await res.json();
|
|
28
34
|
if (!res.ok) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const body = (await res.json());
|
|
32
|
-
detail = body.detail ?? body.message ?? detail;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// ignore parse error — use status code only
|
|
36
|
-
}
|
|
37
|
-
throw new Error(`Clavis API error on ${path}: ${detail}`);
|
|
35
|
+
const detail = body.detail ?? res.statusText;
|
|
36
|
+
throw new Error(`Clavis API error ${res.status}: ${detail}`);
|
|
38
37
|
}
|
|
39
|
-
return
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
async function clavisPost(path, payload) {
|
|
41
|
+
const { default: fetch } = await import("node-fetch");
|
|
42
|
+
const url = `${CLAVIS_API_URL}${path}`;
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: AUTH_HEADERS,
|
|
46
|
+
body: JSON.stringify(payload),
|
|
47
|
+
});
|
|
48
|
+
const body = await res.json();
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const detail = body.detail ?? res.statusText;
|
|
51
|
+
throw new Error(`Clavis API error ${res.status}: ${detail}`);
|
|
52
|
+
}
|
|
53
|
+
return body;
|
|
40
54
|
}
|
|
41
55
|
// ---------------------------------------------------------------------------
|
|
42
|
-
//
|
|
56
|
+
// Tool definitions
|
|
43
57
|
// ---------------------------------------------------------------------------
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...(data.expires_in != null && { expires_in_seconds: data.expires_in }),
|
|
66
|
-
}, null, 2),
|
|
58
|
+
const TOOLS = [
|
|
59
|
+
{
|
|
60
|
+
name: "list_services",
|
|
61
|
+
description: "List all services with stored credentials in Clavis. Returns service names and connector types.",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {},
|
|
65
|
+
required: [],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "check_credential_status",
|
|
70
|
+
description: "Check credential health for a service without making any external API call. " +
|
|
71
|
+
"Returns validity, expiry, and rate-limit info. Safe to call frequently as a health check.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
service_name: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Name of the service to check (e.g. 'openai', 'stripe')",
|
|
78
|
+
},
|
|
67
79
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
required: ["service_name"],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "call_service",
|
|
85
|
+
description: "RECOMMENDED: Make an API call with server-side credential injection. " +
|
|
86
|
+
"The credential is fetched from the Clavis vault and injected into the upstream request. " +
|
|
87
|
+
"Claude never sees the raw API key — this is the secure way to call external APIs.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
service_name: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Name of the Clavis service whose credentials to inject (e.g. 'openai')",
|
|
94
|
+
},
|
|
95
|
+
method: {
|
|
96
|
+
type: "string",
|
|
97
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
98
|
+
description: "HTTP method",
|
|
99
|
+
},
|
|
100
|
+
url: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Full URL to call. Must be on the service's allowed domain (SSRF protection).",
|
|
103
|
+
},
|
|
104
|
+
headers: {
|
|
105
|
+
type: "object",
|
|
106
|
+
description: "Additional request headers (optional)",
|
|
107
|
+
additionalProperties: { type: "string" },
|
|
108
|
+
},
|
|
109
|
+
params: {
|
|
110
|
+
type: "object",
|
|
111
|
+
description: "URL query parameters (optional)",
|
|
112
|
+
additionalProperties: true,
|
|
113
|
+
},
|
|
114
|
+
json: {
|
|
115
|
+
type: "object",
|
|
116
|
+
description: "JSON request body (optional, mutually exclusive with data)",
|
|
117
|
+
additionalProperties: true,
|
|
118
|
+
},
|
|
119
|
+
data: {
|
|
120
|
+
description: "Form-encoded body (optional, mutually exclusive with json)",
|
|
121
|
+
},
|
|
93
122
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
110
|
-
const stub = {
|
|
111
|
-
service_name,
|
|
112
|
-
valid: true,
|
|
113
|
-
expires_in: undefined,
|
|
114
|
-
rate_limit_remaining: undefined,
|
|
115
|
-
note: "check_credential_status is stubbed — /v1/credentials/{service}/check not yet implemented.",
|
|
116
|
-
};
|
|
117
|
-
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "text",
|
|
121
|
-
text: JSON.stringify(stub, null, 2),
|
|
123
|
+
required: ["service_name", "method", "url"],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "get_credentials",
|
|
128
|
+
description: "LEGACY — returns raw credential data. Prefer call_service for security: " +
|
|
129
|
+
"call_service injects credentials server-side so Claude never sees raw keys. " +
|
|
130
|
+
"Use this only if you need the credential value directly.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
service_name: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Name of the service (e.g. 'openai', 'stripe')",
|
|
137
|
+
},
|
|
122
138
|
},
|
|
123
|
-
|
|
139
|
+
required: ["service_name"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Tool handlers
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
async function handleListServices() {
|
|
147
|
+
const data = await clavisGet("/v1/services");
|
|
148
|
+
const items = Array.isArray(data)
|
|
149
|
+
? data
|
|
150
|
+
: data.items
|
|
151
|
+
?? data.services
|
|
152
|
+
?? [];
|
|
153
|
+
if (items.length === 0) {
|
|
154
|
+
return "No services registered in Clavis yet. Add one via the Clavis API or dashboard.";
|
|
155
|
+
}
|
|
156
|
+
const lines = items.map((s) => {
|
|
157
|
+
const svc = s;
|
|
158
|
+
const name = svc.name ?? svc.service_name ?? "unknown";
|
|
159
|
+
const connector = svc.connector_name ? ` (${svc.connector_name})` : "";
|
|
160
|
+
return `- ${name}${connector}`;
|
|
161
|
+
});
|
|
162
|
+
return `${items.length} service(s) registered:\n${lines.join("\n")}`;
|
|
163
|
+
}
|
|
164
|
+
async function handleCheckCredentialStatus(serviceName) {
|
|
165
|
+
const data = await clavisGet(`/v1/credentials/${encodeURIComponent(serviceName)}/check`);
|
|
166
|
+
const lines = [
|
|
167
|
+
`Service: ${serviceName}`,
|
|
168
|
+
`Valid: ${data.valid ? "yes" : "no"}`,
|
|
169
|
+
`Status: ${data.status ?? "unknown"}`,
|
|
170
|
+
];
|
|
171
|
+
if (data.expires_in != null) {
|
|
172
|
+
const mins = Math.floor(data.expires_in / 60);
|
|
173
|
+
lines.push(`Expires in: ${mins > 0 ? `${mins} minutes` : "< 1 minute"}`);
|
|
174
|
+
}
|
|
175
|
+
if (data.rate_limit_remaining != null) {
|
|
176
|
+
lines.push(`Rate limit remaining: ${data.rate_limit_remaining}`);
|
|
177
|
+
}
|
|
178
|
+
if (data.last_used) {
|
|
179
|
+
lines.push(`Last used: ${data.last_used}`);
|
|
180
|
+
}
|
|
181
|
+
if (!data.valid && data.error) {
|
|
182
|
+
lines.push(`Error: ${data.error}`);
|
|
183
|
+
}
|
|
184
|
+
return lines.join("\n");
|
|
185
|
+
}
|
|
186
|
+
async function handleCallService(args) {
|
|
187
|
+
const payload = {
|
|
188
|
+
method: args.method,
|
|
189
|
+
url: args.url,
|
|
190
|
+
headers: args.headers ?? null,
|
|
191
|
+
params: args.params ?? null,
|
|
192
|
+
json: args.json ?? null,
|
|
193
|
+
data: args.data ?? null,
|
|
124
194
|
};
|
|
195
|
+
const result = await clavisPost(`/v1/call/${encodeURIComponent(args.service_name)}`, payload);
|
|
196
|
+
const bodyStr = typeof result.body === "string"
|
|
197
|
+
? result.body
|
|
198
|
+
: JSON.stringify(result.body, null, 2);
|
|
199
|
+
return [`Status: ${result.status_code}`, `Body:\n${bodyStr}`].join("\n");
|
|
200
|
+
}
|
|
201
|
+
async function handleGetCredentials(serviceName) {
|
|
202
|
+
const data = await clavisGet(`/v1/credentials/${encodeURIComponent(serviceName)}`);
|
|
203
|
+
return ("WARNING: This returned raw credential data. " +
|
|
204
|
+
"For future calls, prefer call_service — it injects credentials server-side " +
|
|
205
|
+
"so this conversation never contains raw API keys.\n\n" +
|
|
206
|
+
JSON.stringify(data, null, 2));
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// MCP Server
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
const server = new index_js_1.Server({ name: "clavis", version: "0.1.1" }, { capabilities: { tools: {} } });
|
|
212
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
213
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
214
|
+
const { name, arguments: args } = request.params;
|
|
215
|
+
const a = (args ?? {});
|
|
216
|
+
try {
|
|
217
|
+
let text;
|
|
218
|
+
switch (name) {
|
|
219
|
+
case "list_services":
|
|
220
|
+
text = await handleListServices();
|
|
221
|
+
break;
|
|
222
|
+
case "check_credential_status":
|
|
223
|
+
if (typeof a.service_name !== "string")
|
|
224
|
+
throw new Error("service_name is required");
|
|
225
|
+
text = await handleCheckCredentialStatus(a.service_name);
|
|
226
|
+
break;
|
|
227
|
+
case "call_service":
|
|
228
|
+
if (typeof a.service_name !== "string")
|
|
229
|
+
throw new Error("service_name is required");
|
|
230
|
+
if (typeof a.method !== "string")
|
|
231
|
+
throw new Error("method is required");
|
|
232
|
+
if (typeof a.url !== "string")
|
|
233
|
+
throw new Error("url is required");
|
|
234
|
+
text = await handleCallService({
|
|
235
|
+
service_name: a.service_name,
|
|
236
|
+
method: a.method,
|
|
237
|
+
url: a.url,
|
|
238
|
+
headers: a.headers,
|
|
239
|
+
params: a.params,
|
|
240
|
+
json: a.json,
|
|
241
|
+
data: a.data,
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
case "get_credentials":
|
|
245
|
+
if (typeof a.service_name !== "string")
|
|
246
|
+
throw new Error("service_name is required");
|
|
247
|
+
text = await handleGetCredentials(a.service_name);
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
251
|
+
}
|
|
252
|
+
return { content: [{ type: "text", text }] };
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
256
|
+
return {
|
|
257
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
258
|
+
isError: true,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
125
261
|
});
|
|
126
262
|
// ---------------------------------------------------------------------------
|
|
127
263
|
// Start
|
|
128
264
|
// ---------------------------------------------------------------------------
|
|
129
265
|
async function main() {
|
|
130
|
-
const transport = new StdioServerTransport();
|
|
266
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
131
267
|
await server.connect(transport);
|
|
132
|
-
|
|
268
|
+
process.stderr.write(`[clavis-mcp] Server started. API: ${CLAVIS_API_URL}\n`);
|
|
133
269
|
}
|
|
134
270
|
main().catch((err) => {
|
|
135
|
-
|
|
271
|
+
process.stderr.write(`[clavis-mcp] Fatal: ${err}\n`);
|
|
136
272
|
process.exit(1);
|
|
137
273
|
});
|
|
138
274
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,48 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clavisagent/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "MCP server for secure credential management
|
|
5
|
-
"
|
|
6
|
-
"author": "Clavis",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"homepage": "https://clavisagent.com",
|
|
9
|
-
"keywords": [
|
|
10
|
-
"mcp",
|
|
11
|
-
"model-context-protocol",
|
|
12
|
-
"claude",
|
|
13
|
-
"anthropic",
|
|
14
|
-
"credentials",
|
|
15
|
-
"authentication",
|
|
16
|
-
"api-keys",
|
|
17
|
-
"oauth",
|
|
18
|
-
"token-refresh",
|
|
19
|
-
"rate-limiting",
|
|
20
|
-
"ai-agents",
|
|
21
|
-
"security"
|
|
22
|
-
],
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server for Clavis — secure credential management for Claude Desktop",
|
|
5
|
+
"main": "dist/index.js",
|
|
23
6
|
"bin": {
|
|
24
|
-
"clavis-mcp": "
|
|
7
|
+
"clavis-mcp": "dist/index.js"
|
|
25
8
|
},
|
|
26
|
-
"main": "./dist/index.js",
|
|
27
|
-
"files": [
|
|
28
|
-
"dist/",
|
|
29
|
-
"README.md",
|
|
30
|
-
"LICENSE"
|
|
31
|
-
],
|
|
32
9
|
"scripts": {
|
|
33
10
|
"build": "tsc",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "ts-node src/index.ts"
|
|
36
13
|
},
|
|
37
14
|
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
39
|
-
"
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"node-fetch": "^3.3.2"
|
|
40
17
|
},
|
|
41
18
|
"devDependencies": {
|
|
42
|
-
"@types/node": "^
|
|
43
|
-
"typescript": "^5.
|
|
19
|
+
"@types/node": "^20.0.0",
|
|
20
|
+
"typescript": "^5.4.0",
|
|
21
|
+
"ts-node": "^10.9.0"
|
|
44
22
|
},
|
|
45
23
|
"engines": {
|
|
46
24
|
"node": ">=18"
|
|
47
|
-
}
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/KN0WBOT/clavis"
|
|
30
|
+
},
|
|
31
|
+
"keywords": ["mcp", "clavis", "credentials", "claude", "ai-agents"]
|
|
48
32
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Clavis MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes Clavis credential management to Claude Desktop via the
|
|
6
|
+
* Model Context Protocol. Credentials are injected server-side —
|
|
7
|
+
* Claude never sees raw API keys.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import {
|
|
13
|
+
CallToolRequestSchema,
|
|
14
|
+
ListToolsRequestSchema,
|
|
15
|
+
Tool,
|
|
16
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Config from environment
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const CLAVIS_API_URL = (process.env.CLAVIS_API_URL ?? "http://localhost:8000").replace(/\/$/, "");
|
|
23
|
+
const CLAVIS_API_KEY = process.env.CLAVIS_API_KEY ?? "";
|
|
24
|
+
|
|
25
|
+
if (!CLAVIS_API_KEY) {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
"[clavis-mcp] WARNING: CLAVIS_API_KEY is not set. All requests will fail with 401.\n"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const AUTH_HEADERS: Record<string, string> = {
|
|
32
|
+
Authorization: `Bearer ${CLAVIS_API_KEY}`,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Clavis API helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
async function clavisGet(path: string): Promise<unknown> {
|
|
41
|
+
const { default: fetch } = await import("node-fetch");
|
|
42
|
+
const url = `${CLAVIS_API_URL}${path}`;
|
|
43
|
+
const res = await fetch(url, { headers: AUTH_HEADERS });
|
|
44
|
+
const body = await res.json() as unknown;
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
const detail = (body as { detail?: string }).detail ?? res.statusText;
|
|
47
|
+
throw new Error(`Clavis API error ${res.status}: ${detail}`);
|
|
48
|
+
}
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function clavisPost(path: string, payload: unknown): Promise<unknown> {
|
|
53
|
+
const { default: fetch } = await import("node-fetch");
|
|
54
|
+
const url = `${CLAVIS_API_URL}${path}`;
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: AUTH_HEADERS,
|
|
58
|
+
body: JSON.stringify(payload),
|
|
59
|
+
});
|
|
60
|
+
const body = await res.json() as unknown;
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const detail = (body as { detail?: string }).detail ?? res.statusText;
|
|
63
|
+
throw new Error(`Clavis API error ${res.status}: ${detail}`);
|
|
64
|
+
}
|
|
65
|
+
return body;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Tool definitions
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
const TOOLS: Tool[] = [
|
|
73
|
+
{
|
|
74
|
+
name: "list_services",
|
|
75
|
+
description:
|
|
76
|
+
"List all services with stored credentials in Clavis. Returns service names and connector types.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {},
|
|
80
|
+
required: [],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "check_credential_status",
|
|
85
|
+
description:
|
|
86
|
+
"Check credential health for a service without making any external API call. " +
|
|
87
|
+
"Returns validity, expiry, and rate-limit info. Safe to call frequently as a health check.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
service_name: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Name of the service to check (e.g. 'openai', 'stripe')",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ["service_name"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "call_service",
|
|
101
|
+
description:
|
|
102
|
+
"RECOMMENDED: Make an API call with server-side credential injection. " +
|
|
103
|
+
"The credential is fetched from the Clavis vault and injected into the upstream request. " +
|
|
104
|
+
"Claude never sees the raw API key — this is the secure way to call external APIs.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
service_name: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Name of the Clavis service whose credentials to inject (e.g. 'openai')",
|
|
111
|
+
},
|
|
112
|
+
method: {
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
115
|
+
description: "HTTP method",
|
|
116
|
+
},
|
|
117
|
+
url: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description:
|
|
120
|
+
"Full URL to call. Must be on the service's allowed domain (SSRF protection).",
|
|
121
|
+
},
|
|
122
|
+
headers: {
|
|
123
|
+
type: "object",
|
|
124
|
+
description: "Additional request headers (optional)",
|
|
125
|
+
additionalProperties: { type: "string" },
|
|
126
|
+
},
|
|
127
|
+
params: {
|
|
128
|
+
type: "object",
|
|
129
|
+
description: "URL query parameters (optional)",
|
|
130
|
+
additionalProperties: true,
|
|
131
|
+
},
|
|
132
|
+
json: {
|
|
133
|
+
type: "object",
|
|
134
|
+
description: "JSON request body (optional, mutually exclusive with data)",
|
|
135
|
+
additionalProperties: true,
|
|
136
|
+
},
|
|
137
|
+
data: {
|
|
138
|
+
description: "Form-encoded body (optional, mutually exclusive with json)",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
required: ["service_name", "method", "url"],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "get_credentials",
|
|
146
|
+
description:
|
|
147
|
+
"LEGACY — returns raw credential data. Prefer call_service for security: " +
|
|
148
|
+
"call_service injects credentials server-side so Claude never sees raw keys. " +
|
|
149
|
+
"Use this only if you need the credential value directly.",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
service_name: {
|
|
154
|
+
type: "string",
|
|
155
|
+
description: "Name of the service (e.g. 'openai', 'stripe')",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
required: ["service_name"],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Tool handlers
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
async function handleListServices(): Promise<string> {
|
|
168
|
+
const data = await clavisGet("/v1/services") as { items?: unknown[]; services?: unknown[] } | unknown[];
|
|
169
|
+
const items: unknown[] = Array.isArray(data)
|
|
170
|
+
? data
|
|
171
|
+
: (data as { items?: unknown[]; services?: unknown[] }).items
|
|
172
|
+
?? (data as { services?: unknown[] }).services
|
|
173
|
+
?? [];
|
|
174
|
+
|
|
175
|
+
if (items.length === 0) {
|
|
176
|
+
return "No services registered in Clavis yet. Add one via the Clavis API or dashboard.";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const lines = items.map((s) => {
|
|
180
|
+
const svc = s as { name?: string; service_name?: string; connector_name?: string };
|
|
181
|
+
const name = svc.name ?? svc.service_name ?? "unknown";
|
|
182
|
+
const connector = svc.connector_name ? ` (${svc.connector_name})` : "";
|
|
183
|
+
return `- ${name}${connector}`;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return `${items.length} service(s) registered:\n${lines.join("\n")}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function handleCheckCredentialStatus(serviceName: string): Promise<string> {
|
|
190
|
+
const data = await clavisGet(`/v1/credentials/${encodeURIComponent(serviceName)}/check`) as {
|
|
191
|
+
valid?: boolean;
|
|
192
|
+
status?: string;
|
|
193
|
+
expires_in?: number | null;
|
|
194
|
+
rate_limit_remaining?: number | null;
|
|
195
|
+
last_used?: string | null;
|
|
196
|
+
error?: string;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const lines: string[] = [
|
|
200
|
+
`Service: ${serviceName}`,
|
|
201
|
+
`Valid: ${data.valid ? "yes" : "no"}`,
|
|
202
|
+
`Status: ${data.status ?? "unknown"}`,
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (data.expires_in != null) {
|
|
206
|
+
const mins = Math.floor(data.expires_in / 60);
|
|
207
|
+
lines.push(`Expires in: ${mins > 0 ? `${mins} minutes` : "< 1 minute"}`);
|
|
208
|
+
}
|
|
209
|
+
if (data.rate_limit_remaining != null) {
|
|
210
|
+
lines.push(`Rate limit remaining: ${data.rate_limit_remaining}`);
|
|
211
|
+
}
|
|
212
|
+
if (data.last_used) {
|
|
213
|
+
lines.push(`Last used: ${data.last_used}`);
|
|
214
|
+
}
|
|
215
|
+
if (!data.valid && data.error) {
|
|
216
|
+
lines.push(`Error: ${data.error}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return lines.join("\n");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function handleCallService(args: {
|
|
223
|
+
service_name: string;
|
|
224
|
+
method: string;
|
|
225
|
+
url: string;
|
|
226
|
+
headers?: Record<string, string>;
|
|
227
|
+
params?: Record<string, unknown>;
|
|
228
|
+
json?: Record<string, unknown>;
|
|
229
|
+
data?: unknown;
|
|
230
|
+
}): Promise<string> {
|
|
231
|
+
const payload = {
|
|
232
|
+
method: args.method,
|
|
233
|
+
url: args.url,
|
|
234
|
+
headers: args.headers ?? null,
|
|
235
|
+
params: args.params ?? null,
|
|
236
|
+
json: args.json ?? null,
|
|
237
|
+
data: args.data ?? null,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = await clavisPost(
|
|
241
|
+
`/v1/call/${encodeURIComponent(args.service_name)}`,
|
|
242
|
+
payload
|
|
243
|
+
) as {
|
|
244
|
+
status_code: number;
|
|
245
|
+
headers: Record<string, string>;
|
|
246
|
+
body: unknown;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const bodyStr =
|
|
250
|
+
typeof result.body === "string"
|
|
251
|
+
? result.body
|
|
252
|
+
: JSON.stringify(result.body, null, 2);
|
|
253
|
+
|
|
254
|
+
return [`Status: ${result.status_code}`, `Body:\n${bodyStr}`].join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function handleGetCredentials(serviceName: string): Promise<string> {
|
|
258
|
+
const data = await clavisGet(`/v1/credentials/${encodeURIComponent(serviceName)}`);
|
|
259
|
+
return (
|
|
260
|
+
"WARNING: This returned raw credential data. " +
|
|
261
|
+
"For future calls, prefer call_service — it injects credentials server-side " +
|
|
262
|
+
"so this conversation never contains raw API keys.\n\n" +
|
|
263
|
+
JSON.stringify(data, null, 2)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// MCP Server
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
const server = new Server(
|
|
272
|
+
{ name: "clavis", version: "0.1.1" },
|
|
273
|
+
{ capabilities: { tools: {} } }
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
277
|
+
|
|
278
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
279
|
+
const { name, arguments: args } = request.params;
|
|
280
|
+
const a = (args ?? {}) as Record<string, unknown>;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
let text: string;
|
|
284
|
+
|
|
285
|
+
switch (name) {
|
|
286
|
+
case "list_services":
|
|
287
|
+
text = await handleListServices();
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case "check_credential_status":
|
|
291
|
+
if (typeof a.service_name !== "string") throw new Error("service_name is required");
|
|
292
|
+
text = await handleCheckCredentialStatus(a.service_name);
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case "call_service":
|
|
296
|
+
if (typeof a.service_name !== "string") throw new Error("service_name is required");
|
|
297
|
+
if (typeof a.method !== "string") throw new Error("method is required");
|
|
298
|
+
if (typeof a.url !== "string") throw new Error("url is required");
|
|
299
|
+
text = await handleCallService({
|
|
300
|
+
service_name: a.service_name,
|
|
301
|
+
method: a.method,
|
|
302
|
+
url: a.url,
|
|
303
|
+
headers: a.headers as Record<string, string> | undefined,
|
|
304
|
+
params: a.params as Record<string, unknown> | undefined,
|
|
305
|
+
json: a.json as Record<string, unknown> | undefined,
|
|
306
|
+
data: a.data,
|
|
307
|
+
});
|
|
308
|
+
break;
|
|
309
|
+
|
|
310
|
+
case "get_credentials":
|
|
311
|
+
if (typeof a.service_name !== "string") throw new Error("service_name is required");
|
|
312
|
+
text = await handleGetCredentials(a.service_name);
|
|
313
|
+
break;
|
|
314
|
+
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { content: [{ type: "text", text }] };
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
322
|
+
return {
|
|
323
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
324
|
+
isError: true,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Start
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
async function main(): Promise<void> {
|
|
334
|
+
const transport = new StdioServerTransport();
|
|
335
|
+
await server.connect(transport);
|
|
336
|
+
process.stderr.write(`[clavis-mcp] Server started. API: ${CLAVIS_API_URL}\n`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
main().catch((err) => {
|
|
340
|
+
process.stderr.write(`[clavis-mcp] Fatal: ${err}\n`);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|