infisical-mcp 1.0.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 +21 -0
- package/dist/api.d.ts +9 -0
- package/dist/api.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +105 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# infisical-mcp
|
|
2
|
+
|
|
3
|
+
Infisical API: list, read, create, update and delete secrets via universal auth.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export INFISICAL_CLIENT_ID=...
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Needs an Infisical machine identity (client id + secret). Optional INFISICAL_SITE_URL for self-hosted instances.
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
- `list_secrets` — Secrets in an Infisical project environment and path.
|
|
16
|
+
- `get_secret` — Single Infisical secret value by key.
|
|
17
|
+
- `create_secret` — Create an Infisical secret. Fails if the key already exists (use update).
|
|
18
|
+
- `update_secret` — Change an existing Infisical secret value.
|
|
19
|
+
- `delete_secret` — Delete an Infisical secret by key.
|
|
20
|
+
|
|
21
|
+
Part of [awesome-mcps](https://github.com/mrfentmen/awesome-mcps).
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class InfisicalError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare function errorMessage(e: unknown): string;
|
|
5
|
+
export declare function listSecrets(projectId: string, environment?: string, secretPath?: string): Promise<string>;
|
|
6
|
+
export declare function getSecret(projectId: string, key: string, environment?: string, secretPath?: string): Promise<string>;
|
|
7
|
+
export declare function createSecret(projectId: string, key: string, value: string, environment?: string, secretPath?: string): Promise<string>;
|
|
8
|
+
export declare function updateSecret(projectId: string, key: string, value: string, environment?: string, secretPath?: string): Promise<string>;
|
|
9
|
+
export declare function deleteSecret(projectId: string, key: string, environment?: string, secretPath?: string): Promise<string>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export class InfisicalError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "InfisicalError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function errorMessage(e) {
|
|
8
|
+
return e instanceof Error ? e.message : String(e);
|
|
9
|
+
}
|
|
10
|
+
const UA = { "User-Agent": "awesome-mcps/1.0" };
|
|
11
|
+
function envStrict(name) {
|
|
12
|
+
const v = process.env[name];
|
|
13
|
+
if (!v)
|
|
14
|
+
throw new InfisicalError(`Set the ${name} environment variable.`);
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
17
|
+
let cachedToken = "";
|
|
18
|
+
let tokenExpiresAt = 0;
|
|
19
|
+
async function authHeaders() {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (!cachedToken || now >= tokenExpiresAt) {
|
|
22
|
+
const site = process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com";
|
|
23
|
+
const res = await fetch(`${site}/api/v1/auth/universal-auth/login`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { ...UA, "Content-Type": "application/json" },
|
|
26
|
+
body: JSON.stringify({ clientId: envStrict("INFISICAL_CLIENT_ID"), clientSecret: envStrict("INFISICAL_CLIENT_SECRET") }),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
throw new InfisicalError(`Infisical login failed: HTTP ${res.status}`);
|
|
30
|
+
const data = (await res.json());
|
|
31
|
+
if (!data.accessToken)
|
|
32
|
+
throw new InfisicalError("Infisical login returned no access token");
|
|
33
|
+
cachedToken = data.accessToken;
|
|
34
|
+
tokenExpiresAt = now + (data.expiresIn ?? 7200) * 1000 - 60000;
|
|
35
|
+
}
|
|
36
|
+
return { Authorization: "Bearer " + cachedToken };
|
|
37
|
+
}
|
|
38
|
+
function pretty(data) {
|
|
39
|
+
const t = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
40
|
+
return t.length > 12000 ? t.slice(0, 12000) + "\n…(truncated)" : t;
|
|
41
|
+
}
|
|
42
|
+
async function req(url, init = {}) {
|
|
43
|
+
const res = await fetch(url, { headers: { ...UA, ...(await authHeaders()), ...(init.headers ?? {}) }, ...init });
|
|
44
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
45
|
+
const data = ct.includes("json") ? await res.json() : await res.text();
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const detail = typeof data === "string" ? data.slice(0, 300) : JSON.stringify(data).slice(0, 300);
|
|
48
|
+
throw new InfisicalError(`HTTP ${res.status}: ${detail}`);
|
|
49
|
+
}
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
export async function listSecrets(projectId, environment, secretPath) {
|
|
53
|
+
const site = (process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com").replace(/\/$/, "");
|
|
54
|
+
let url = `${site}/api/v3/secrets/raw`;
|
|
55
|
+
const qs = new URLSearchParams();
|
|
56
|
+
qs.append("workspaceId", String(projectId));
|
|
57
|
+
if (environment !== undefined)
|
|
58
|
+
qs.append("environment", String(environment));
|
|
59
|
+
if (secretPath !== undefined)
|
|
60
|
+
qs.append("secretPath", String(secretPath));
|
|
61
|
+
const qstr = qs.toString();
|
|
62
|
+
if (qstr)
|
|
63
|
+
url += (url.includes('?') ? '&' : '?') + qstr;
|
|
64
|
+
const data = await req(url);
|
|
65
|
+
return pretty(data);
|
|
66
|
+
}
|
|
67
|
+
export async function getSecret(projectId, key, environment, secretPath) {
|
|
68
|
+
const site = (process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com").replace(/\/$/, "");
|
|
69
|
+
let url = `${site}/api/v3/secrets/raw/${encodeURIComponent(String(key))}`;
|
|
70
|
+
const qs = new URLSearchParams();
|
|
71
|
+
qs.append("workspaceId", String(projectId));
|
|
72
|
+
if (environment !== undefined)
|
|
73
|
+
qs.append("environment", String(environment));
|
|
74
|
+
if (secretPath !== undefined)
|
|
75
|
+
qs.append("secretPath", String(secretPath));
|
|
76
|
+
const qstr = qs.toString();
|
|
77
|
+
if (qstr)
|
|
78
|
+
url += (url.includes('?') ? '&' : '?') + qstr;
|
|
79
|
+
const data = await req(url);
|
|
80
|
+
return pretty(data);
|
|
81
|
+
}
|
|
82
|
+
export async function createSecret(projectId, key, value, environment, secretPath) {
|
|
83
|
+
const site = (process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com").replace(/\/$/, "");
|
|
84
|
+
let url = `${site}/api/v3/secrets/raw/${encodeURIComponent(String(key))}`;
|
|
85
|
+
const data = await req(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: projectId, environment, secretPath, secretKey: key, secretValue: value }) });
|
|
86
|
+
return pretty(data);
|
|
87
|
+
}
|
|
88
|
+
export async function updateSecret(projectId, key, value, environment, secretPath) {
|
|
89
|
+
const site = (process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com").replace(/\/$/, "");
|
|
90
|
+
let url = `${site}/api/v3/secrets/raw/${encodeURIComponent(String(key))}`;
|
|
91
|
+
const data = await req(url, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: projectId, environment, secretPath, secretKey: key, secretValue: value }) });
|
|
92
|
+
return pretty(data);
|
|
93
|
+
}
|
|
94
|
+
export async function deleteSecret(projectId, key, environment, secretPath) {
|
|
95
|
+
const site = (process.env.INFISICAL_SITE_URL ?? "https://app.infisical.com").replace(/\/$/, "");
|
|
96
|
+
let url = `${site}/api/v3/secrets/raw/${encodeURIComponent(String(key))}`;
|
|
97
|
+
const qs = new URLSearchParams();
|
|
98
|
+
qs.append("workspaceId", String(projectId));
|
|
99
|
+
if (environment !== undefined)
|
|
100
|
+
qs.append("environment", String(environment));
|
|
101
|
+
if (secretPath !== undefined)
|
|
102
|
+
qs.append("secretPath", String(secretPath));
|
|
103
|
+
const qstr = qs.toString();
|
|
104
|
+
if (qstr)
|
|
105
|
+
url += (url.includes('?') ? '&' : '?') + qstr;
|
|
106
|
+
const data = await req(url, { method: "DELETE" });
|
|
107
|
+
return pretty(data);
|
|
108
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { createServer } from "./server.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
const server = createServer();
|
|
5
|
+
const transport = new StdioServerTransport();
|
|
6
|
+
await server.connect(transport);
|
|
7
|
+
console.error("MCP server running on stdio");
|
|
8
|
+
}
|
|
9
|
+
main().catch((err) => {
|
|
10
|
+
console.error("Fatal error:", err);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { errorMessage, listSecrets, getSecret, createSecret, updateSecret, deleteSecret, } from "./api.js";
|
|
4
|
+
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
5
|
+
const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
|
|
6
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
7
|
+
const NON_READ_ONLY = { openWorldHint: true };
|
|
8
|
+
export function createServer() {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: "infisical-mcp",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
});
|
|
13
|
+
server.registerTool("list_secrets", {
|
|
14
|
+
title: "List secrets",
|
|
15
|
+
description: "Secrets in an Infisical project environment and path.",
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
projectId: z.string().describe("Project id"),
|
|
18
|
+
environment: z.string().default("dev").describe("Environment slug"),
|
|
19
|
+
secretPath: z.string().default("/").describe("Secret path"),
|
|
20
|
+
}),
|
|
21
|
+
annotations: READ_ONLY,
|
|
22
|
+
}, async ({ projectId, environment, secretPath }) => {
|
|
23
|
+
try {
|
|
24
|
+
return text(await listSecrets(projectId, environment, secretPath));
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
return textError(errorMessage(e));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
server.registerTool("get_secret", {
|
|
31
|
+
title: "Get one secret",
|
|
32
|
+
description: "Single Infisical secret value by key.",
|
|
33
|
+
inputSchema: z.object({
|
|
34
|
+
projectId: z.string().describe("Project id"),
|
|
35
|
+
environment: z.string().default("dev").describe("Environment"),
|
|
36
|
+
secretPath: z.string().default("/").describe("Path"),
|
|
37
|
+
key: z.string().describe("Secret key"),
|
|
38
|
+
}),
|
|
39
|
+
annotations: READ_ONLY,
|
|
40
|
+
}, async ({ projectId, environment, secretPath, key }) => {
|
|
41
|
+
try {
|
|
42
|
+
return text(await getSecret(projectId, environment, secretPath, key));
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
return textError(errorMessage(e));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
server.registerTool("create_secret", {
|
|
49
|
+
title: "Create secret",
|
|
50
|
+
description: "Create an Infisical secret. Fails if the key already exists (use update).",
|
|
51
|
+
inputSchema: z.object({
|
|
52
|
+
projectId: z.string().describe("Project id"),
|
|
53
|
+
environment: z.string().default("dev").describe("Environment"),
|
|
54
|
+
secretPath: z.string().default("/").describe("Path"),
|
|
55
|
+
key: z.string().describe("Secret key"),
|
|
56
|
+
value: z.string().describe("Secret value"),
|
|
57
|
+
}),
|
|
58
|
+
annotations: NON_READ_ONLY,
|
|
59
|
+
}, async ({ projectId, environment, secretPath, key, value }) => {
|
|
60
|
+
try {
|
|
61
|
+
return text(await createSecret(projectId, environment, secretPath, key, value));
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return textError(errorMessage(e));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
server.registerTool("update_secret", {
|
|
68
|
+
title: "Update secret",
|
|
69
|
+
description: "Change an existing Infisical secret value.",
|
|
70
|
+
inputSchema: z.object({
|
|
71
|
+
projectId: z.string().describe("Project id"),
|
|
72
|
+
environment: z.string().default("dev").describe("Environment"),
|
|
73
|
+
secretPath: z.string().default("/").describe("Path"),
|
|
74
|
+
key: z.string().describe("Secret key"),
|
|
75
|
+
value: z.string().describe("New value"),
|
|
76
|
+
}),
|
|
77
|
+
annotations: NON_READ_ONLY,
|
|
78
|
+
}, async ({ projectId, environment, secretPath, key, value }) => {
|
|
79
|
+
try {
|
|
80
|
+
return text(await updateSecret(projectId, environment, secretPath, key, value));
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return textError(errorMessage(e));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
server.registerTool("delete_secret", {
|
|
87
|
+
title: "Delete secret",
|
|
88
|
+
description: "Delete an Infisical secret by key.",
|
|
89
|
+
inputSchema: z.object({
|
|
90
|
+
projectId: z.string().describe("Project id"),
|
|
91
|
+
environment: z.string().default("dev").describe("Environment"),
|
|
92
|
+
secretPath: z.string().default("/").describe("Path"),
|
|
93
|
+
key: z.string().describe("Secret key"),
|
|
94
|
+
}),
|
|
95
|
+
annotations: NON_READ_ONLY,
|
|
96
|
+
}, async ({ projectId, environment, secretPath, key }) => {
|
|
97
|
+
try {
|
|
98
|
+
return text(await deleteSecret(projectId, environment, secretPath, key));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return textError(errorMessage(e));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return server;
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "infisical-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Infisical API: list, read, create, update and delete secrets via universal auth.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"mcpName": "io.github.mrfentmen/infisical-mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mrfentmen/awesome-mcps.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"infisical-mcp": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"dev": "rm -rf dist && tsc -p tsconfig.json && node dist/index.js",
|
|
22
|
+
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"infisical",
|
|
27
|
+
"secrets",
|
|
28
|
+
"devops",
|
|
29
|
+
"security"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
34
|
+
"zod": "^3.23.8"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"typescript": "^5.6.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
}
|
|
43
|
+
}
|