@clavisagent/mcp-server 0.1.1 → 0.1.2
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 +26 -63
- package/dist/index.d.ts +3 -3
- package/dist/index.js +107 -243
- package/dist/index.js.map +1 -1
- package/package.json +39 -18
- package/src/index.ts +0 -342
- package/tsconfig.json +0 -17
package/README.md
CHANGED
|
@@ -1,91 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Clavis MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Secure credential management for Claude Desktop and MCP servers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 Encrypted credential storage (AES-256)
|
|
8
|
+
- 🔄 Automatic OAuth token refresh
|
|
9
|
+
- ⚡ Distributed rate limiting
|
|
10
|
+
- 📊 Full audit logging
|
|
6
11
|
|
|
7
12
|
## Installation
|
|
8
13
|
|
|
14
|
+
```bash
|
|
15
|
+
npx @clavisagent/mcp-server
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install globally:
|
|
19
|
+
|
|
9
20
|
```bash
|
|
10
21
|
npm install -g @clavisagent/mcp-server
|
|
11
22
|
```
|
|
12
23
|
|
|
13
|
-
## Claude Desktop
|
|
24
|
+
## Usage with Claude Desktop
|
|
14
25
|
|
|
15
|
-
Add to your `claude_desktop_config.json
|
|
26
|
+
Add the following to your Claude Desktop configuration file (`claude_desktop_config.json`):
|
|
16
27
|
|
|
17
28
|
```json
|
|
18
29
|
{
|
|
19
30
|
"mcpServers": {
|
|
20
31
|
"clavis": {
|
|
21
32
|
"command": "npx",
|
|
22
|
-
"args": ["-y", "@clavisagent/mcp-server"]
|
|
23
|
-
"env": {
|
|
24
|
-
"CLAVIS_API_KEY": "eyJ...",
|
|
25
|
-
"CLAVIS_API_URL": "https://your-clavis-instance.com"
|
|
26
|
-
}
|
|
33
|
+
"args": ["-y", "@clavisagent/mcp-server"]
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
```
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
34
|
-
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
35
|
-
|
|
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", ...}
|
|
57
|
-
```
|
|
58
|
-
|
|
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:
|
|
39
|
+
## Usage with Claude Code
|
|
65
40
|
|
|
66
|
-
```
|
|
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
|
-
}
|
|
41
|
+
```bash
|
|
42
|
+
claude mcp add clavis -- npx -y @clavisagent/mcp-server
|
|
79
43
|
```
|
|
80
44
|
|
|
81
|
-
##
|
|
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
|
|
45
|
+
## Available Tools
|
|
86
46
|
|
|
87
|
-
|
|
88
|
-
|
|
47
|
+
| Tool | Description |
|
|
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 |
|
|
89
52
|
|
|
90
53
|
## License
|
|
91
54
|
|
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 and other MCP clients.
|
|
6
|
+
* Configure via environment variable:
|
|
7
|
+
* CLAVIS_API_KEY — your Clavis JWT (from POST /v1/auth/login)
|
|
8
8
|
*/
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,274 +1,138 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
2
|
/**
|
|
4
3
|
* Clavis MCP Server
|
|
5
4
|
*
|
|
6
|
-
* Exposes Clavis credential management to Claude Desktop
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Exposes Clavis credential management to Claude Desktop and other MCP clients.
|
|
6
|
+
* Configure via environment variable:
|
|
7
|
+
* CLAVIS_API_KEY — your Clavis JWT (from POST /v1/auth/login)
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { z } from "zod";
|
|
14
12
|
// ---------------------------------------------------------------------------
|
|
15
|
-
// Config
|
|
13
|
+
// Config
|
|
16
14
|
// ---------------------------------------------------------------------------
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
-
|
|
15
|
+
const BASE_URL = "https://clavisagent.com";
|
|
16
|
+
const apiKey = process.env.CLAVIS_API_KEY;
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
console.error("[clavis-mcp] Error: CLAVIS_API_KEY environment variable is not set.");
|
|
19
|
+
process.exit(1);
|
|
21
20
|
}
|
|
22
|
-
const
|
|
23
|
-
Authorization: `Bearer ${
|
|
21
|
+
const authHeaders = {
|
|
22
|
+
Authorization: `Bearer ${apiKey}`,
|
|
24
23
|
"Content-Type": "application/json",
|
|
25
24
|
};
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Clavis API helpers
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
25
|
async function clavisGet(path) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const res = await fetch(url, { headers: AUTH_HEADERS });
|
|
33
|
-
const body = await res.json();
|
|
26
|
+
const url = `${BASE_URL}${path}`;
|
|
27
|
+
const res = await fetch(url, { headers: authHeaders });
|
|
34
28
|
if (!res.ok) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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}`);
|
|
29
|
+
let detail = `HTTP ${res.status}`;
|
|
30
|
+
try {
|
|
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}`);
|
|
52
38
|
}
|
|
53
|
-
return
|
|
39
|
+
return res.json();
|
|
54
40
|
}
|
|
55
41
|
// ---------------------------------------------------------------------------
|
|
56
|
-
//
|
|
42
|
+
// MCP Server
|
|
57
43
|
// ---------------------------------------------------------------------------
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
},
|
|
122
|
-
},
|
|
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
|
-
},
|
|
44
|
+
const server = new McpServer({
|
|
45
|
+
name: "clavis",
|
|
46
|
+
version: "0.1.0",
|
|
47
|
+
});
|
|
48
|
+
// ── Tool 1: get_credentials ────────────────────────────────────────────────
|
|
49
|
+
server.tool("get_credentials", "Retrieve a valid access token or API key for a named service from Clavis. " +
|
|
50
|
+
"Clavis handles token refresh and rotation automatically.", {
|
|
51
|
+
service_name: z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1)
|
|
54
|
+
.describe("The name of the service to retrieve credentials for (e.g. 'github', 'openai')"),
|
|
55
|
+
}, async ({ service_name }) => {
|
|
56
|
+
const data = await clavisGet(`/v1/tokens/${service_name}`);
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: JSON.stringify({
|
|
62
|
+
service: service_name,
|
|
63
|
+
access_token: data.access_token,
|
|
64
|
+
token_type: data.token_type ?? "Bearer",
|
|
65
|
+
...(data.expires_in != null && { expires_in_seconds: data.expires_in }),
|
|
66
|
+
}, null, 2),
|
|
138
67
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Tool handlers
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
async function handleListServices() {
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
// ── Tool 2: list_services ─────────────────────────────────────────────────
|
|
72
|
+
server.tool("list_services", "List all services configured in Clavis for the authenticated developer.", {}, async () => {
|
|
147
73
|
const data = await clavisGet("/v1/services");
|
|
148
|
-
|
|
74
|
+
// Normalise: API may return an array directly or wrapped in { services: [...] }
|
|
75
|
+
const services = Array.isArray(data)
|
|
149
76
|
? data
|
|
150
|
-
: data.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
77
|
+
: data.services ?? [];
|
|
78
|
+
const active = services.filter((s) => s.is_active);
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify({
|
|
84
|
+
total: services.length,
|
|
85
|
+
active: active.length,
|
|
86
|
+
services: services.map((s) => ({
|
|
87
|
+
name: s.name,
|
|
88
|
+
id: s.id,
|
|
89
|
+
is_active: s.is_active,
|
|
90
|
+
created_at: s.created_at,
|
|
91
|
+
})),
|
|
92
|
+
}, null, 2),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
// ── Tool 3: check_credential_status ───────────────────────────────────────
|
|
98
|
+
server.tool("check_credential_status", "Check the validity and rate-limit status of credentials for a service. " +
|
|
99
|
+
"NOTE: The /v1/credentials/{service}/check endpoint is not yet implemented — " +
|
|
100
|
+
"returns a stub response until the API endpoint is live.", {
|
|
101
|
+
service_name: z
|
|
102
|
+
.string()
|
|
103
|
+
.min(1)
|
|
104
|
+
.describe("The name of the service to check (e.g. 'github', 'openai')"),
|
|
105
|
+
}, async ({ service_name }) => {
|
|
106
|
+
// Stub — endpoint /v1/credentials/{service_name}/check is not yet implemented.
|
|
107
|
+
// When the endpoint goes live, replace the body below with:
|
|
108
|
+
// const data = await clavisGet<CredentialStatus>(`/v1/credentials/${service_name}/check`);
|
|
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),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
194
124
|
};
|
|
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
|
-
}
|
|
261
125
|
});
|
|
262
126
|
// ---------------------------------------------------------------------------
|
|
263
127
|
// Start
|
|
264
128
|
// ---------------------------------------------------------------------------
|
|
265
129
|
async function main() {
|
|
266
|
-
const transport = new
|
|
130
|
+
const transport = new StdioServerTransport();
|
|
267
131
|
await server.connect(transport);
|
|
268
|
-
|
|
132
|
+
console.error("[clavis-mcp] Server running on stdio");
|
|
269
133
|
}
|
|
270
134
|
main().catch((err) => {
|
|
271
|
-
|
|
135
|
+
console.error("[clavis-mcp] Fatal error:", err);
|
|
272
136
|
process.exit(1);
|
|
273
137
|
});
|
|
274
138
|
//# 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,32 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clavisagent/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"mcpName": "io.github.KN0WBOT/clavis",
|
|
5
|
+
"description": "MCP server for secure credential management. Handles encrypted storage, auto token refresh, and rate limiting for Claude Desktop and AI agents.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Clavis",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://clavisagent.com",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/KN0WBOT/clavis-mcp.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"claude",
|
|
18
|
+
"anthropic",
|
|
19
|
+
"credentials",
|
|
20
|
+
"authentication",
|
|
21
|
+
"api-keys",
|
|
22
|
+
"oauth",
|
|
23
|
+
"token-refresh",
|
|
24
|
+
"rate-limiting",
|
|
25
|
+
"ai-agents",
|
|
26
|
+
"security"
|
|
27
|
+
],
|
|
6
28
|
"bin": {
|
|
7
|
-
"clavis-mcp": "dist/index.js"
|
|
29
|
+
"clavis-mcp": "./dist/index.js"
|
|
8
30
|
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
9
37
|
"scripts": {
|
|
10
38
|
"build": "tsc",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
39
|
+
"dev": "tsc --watch",
|
|
40
|
+
"start": "node dist/index.js"
|
|
13
41
|
},
|
|
14
42
|
"dependencies": {
|
|
15
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
16
|
-
"
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
44
|
+
"zod": "^3.23.8"
|
|
17
45
|
},
|
|
18
46
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^
|
|
20
|
-
"typescript": "^5.
|
|
21
|
-
"ts-node": "^10.9.0"
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"typescript": "^5.5.0"
|
|
22
49
|
},
|
|
23
50
|
"engines": {
|
|
24
51
|
"node": ">=18"
|
|
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"]
|
|
52
|
+
}
|
|
32
53
|
}
|
package/src/index.ts
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
}
|