@sapiom/mcp 0.2.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/LICENSE +21 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +138 -0
- package/dist/auth.js.map +1 -0
- package/dist/credentials.d.ts +36 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +97 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/authenticate.d.ts +4 -0
- package/dist/tools/authenticate.d.ts.map +1 -0
- package/dist/tools/authenticate.js +50 -0
- package/dist/tools/authenticate.js.map +1 -0
- package/dist/tools/status.d.ts +4 -0
- package/dist/tools/status.d.ts.map +1 -0
- package/dist/tools/status.js +36 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/verify.d.ts +4 -0
- package/dist/tools/verify.d.ts.map +1 -0
- package/dist/tools/verify.js +153 -0
- package/dist/tools/verify.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sapiom
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAgHrB"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import * as crypto from "node:crypto";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { URL, URLSearchParams } from "node:url";
|
|
5
|
+
function openBrowser(url) {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
try {
|
|
8
|
+
if (platform === "darwin") {
|
|
9
|
+
execSync(`open "${url}"`);
|
|
10
|
+
}
|
|
11
|
+
else if (platform === "win32") {
|
|
12
|
+
execSync(`start "" "${url}"`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
execSync(`xdg-open "${url}"`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Browser open failed — user will see the URL in the output
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function performBrowserAuth(appURL, apiURL) {
|
|
23
|
+
const state = crypto.randomBytes(32).toString("hex");
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const server = http.createServer();
|
|
26
|
+
let settled = false;
|
|
27
|
+
const timeout = setTimeout(() => {
|
|
28
|
+
if (!settled) {
|
|
29
|
+
settled = true;
|
|
30
|
+
server.close();
|
|
31
|
+
reject(new Error(`Authentication timed out after 5 minutes. Open this URL manually to try again:\n${browserURL}`));
|
|
32
|
+
}
|
|
33
|
+
}, 5 * 60 * 1000);
|
|
34
|
+
// Placeholder — set after we know the port
|
|
35
|
+
let browserURL = "";
|
|
36
|
+
server.on("request", async (req, res) => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
const reqURL = new URL(req.url ?? "/", `http://localhost`);
|
|
40
|
+
if (reqURL.pathname !== "/callback") {
|
|
41
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
42
|
+
res.end("Not found");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const code = reqURL.searchParams.get("code");
|
|
46
|
+
const returnedState = reqURL.searchParams.get("state");
|
|
47
|
+
// Serve the "you can close this tab" page immediately
|
|
48
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
49
|
+
res.end(`<!DOCTYPE html>
|
|
50
|
+
<html>
|
|
51
|
+
<head><title>Sapiom CLI Auth</title></head>
|
|
52
|
+
<body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
53
|
+
<div style="text-align: center;">
|
|
54
|
+
<h1>Authentication complete</h1>
|
|
55
|
+
<p>You can close this tab and return to your terminal.</p>
|
|
56
|
+
</div>
|
|
57
|
+
</body>
|
|
58
|
+
</html>`);
|
|
59
|
+
if (!returnedState || returnedState !== state) {
|
|
60
|
+
settled = true;
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
server.close();
|
|
63
|
+
reject(new Error("State mismatch — possible CSRF attack. Please try again."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!code) {
|
|
67
|
+
settled = true;
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
server.close();
|
|
70
|
+
reject(new Error("No authorization code received from the browser."));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Exchange the code for an API key
|
|
74
|
+
try {
|
|
75
|
+
const address = server.address();
|
|
76
|
+
const redirectUri = `http://localhost:${address.port}/callback`;
|
|
77
|
+
const result = await exchangeCodeForApiKey(apiURL, code, redirectUri);
|
|
78
|
+
settled = true;
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
server.close();
|
|
81
|
+
resolve(result);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
settled = true;
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
server.close();
|
|
87
|
+
reject(err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Listen on random port
|
|
91
|
+
server.listen(0, "127.0.0.1", () => {
|
|
92
|
+
const address = server.address();
|
|
93
|
+
const port = address.port;
|
|
94
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
95
|
+
const params = new URLSearchParams({
|
|
96
|
+
redirect_uri: redirectUri,
|
|
97
|
+
state,
|
|
98
|
+
});
|
|
99
|
+
browserURL = `${appURL}/auth/cli?${params.toString()}`;
|
|
100
|
+
console.error(`Opening browser for authentication...`);
|
|
101
|
+
console.error(`If the browser doesn't open, visit: ${browserURL}`);
|
|
102
|
+
openBrowser(browserURL);
|
|
103
|
+
});
|
|
104
|
+
server.on("error", (err) => {
|
|
105
|
+
if (!settled) {
|
|
106
|
+
settled = true;
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
reject(new Error(`Failed to start local server: ${err.message}`));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function exchangeCodeForApiKey(apiURL, code, redirectUri) {
|
|
114
|
+
const response = await fetch(`${apiURL}/v1/auth/cli/token`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({ code, redirectUri }),
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
let message = `Token exchange failed (${response.status})`;
|
|
121
|
+
try {
|
|
122
|
+
const body = (await response.json());
|
|
123
|
+
if (body.message) {
|
|
124
|
+
message = `Token exchange failed: ${body.message}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Ignore JSON parse errors
|
|
129
|
+
}
|
|
130
|
+
throw new Error(message);
|
|
131
|
+
}
|
|
132
|
+
const data = (await response.json());
|
|
133
|
+
if (!data.apiKey || !data.tenantId) {
|
|
134
|
+
throw new Error("Invalid response from token exchange endpoint");
|
|
135
|
+
}
|
|
136
|
+
return data;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface CredentialEntry {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
tenantId: string;
|
|
4
|
+
organizationName: string;
|
|
5
|
+
apiKeyId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface EnvironmentConfig {
|
|
8
|
+
appURL: string;
|
|
9
|
+
apiURL: string;
|
|
10
|
+
services?: Record<string, string>;
|
|
11
|
+
credentials?: CredentialEntry;
|
|
12
|
+
}
|
|
13
|
+
export interface CredentialsFile {
|
|
14
|
+
currentEnvironment: string;
|
|
15
|
+
environments: Record<string, EnvironmentConfig>;
|
|
16
|
+
}
|
|
17
|
+
export interface ResolvedEnvironment {
|
|
18
|
+
name: string;
|
|
19
|
+
appURL: string;
|
|
20
|
+
apiURL: string;
|
|
21
|
+
services: Record<string, string>;
|
|
22
|
+
credentials: CredentialEntry | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the active environment configuration.
|
|
26
|
+
*
|
|
27
|
+
* Priority: SAPIOM_ENVIRONMENT env var > file's currentEnvironment > "production"
|
|
28
|
+
*
|
|
29
|
+
* If the file doesn't exist and environment is "production", returns hardcoded defaults.
|
|
30
|
+
* If the file doesn't exist and environment is custom, throws (file must define custom envs).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveEnvironment(envOverride?: string): Promise<ResolvedEnvironment>;
|
|
33
|
+
export declare function readCredentials(envName: string): Promise<CredentialEntry | null>;
|
|
34
|
+
export declare function writeCredentials(envName: string, appURL: string, apiURL: string, entry: CredentialEntry): Promise<void>;
|
|
35
|
+
export declare function clearCredentials(envName: string): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;CACrC;AAuBD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC,CA+C9B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAGjC;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrE"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
const DEFAULT_APP_URL = "https://app.sapiom.ai";
|
|
5
|
+
const DEFAULT_API_URL = "https://api.sapiom.ai";
|
|
6
|
+
const DEFAULT_ENVIRONMENT = "production";
|
|
7
|
+
function getCredentialsPath() {
|
|
8
|
+
return path.join(os.homedir(), ".sapiom", "credentials.json");
|
|
9
|
+
}
|
|
10
|
+
async function readCredentialsFile() {
|
|
11
|
+
try {
|
|
12
|
+
const content = await fs.readFile(getCredentialsPath(), "utf-8");
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function writeCredentialsFile(file) {
|
|
20
|
+
const filePath = getCredentialsPath();
|
|
21
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
22
|
+
await fs.writeFile(filePath, JSON.stringify(file, null, 2) + "\n", {
|
|
23
|
+
mode: 0o600,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the active environment configuration.
|
|
28
|
+
*
|
|
29
|
+
* Priority: SAPIOM_ENVIRONMENT env var > file's currentEnvironment > "production"
|
|
30
|
+
*
|
|
31
|
+
* If the file doesn't exist and environment is "production", returns hardcoded defaults.
|
|
32
|
+
* If the file doesn't exist and environment is custom, throws (file must define custom envs).
|
|
33
|
+
*/
|
|
34
|
+
export async function resolveEnvironment(envOverride) {
|
|
35
|
+
const file = await readCredentialsFile();
|
|
36
|
+
const name = envOverride ?? file?.currentEnvironment ?? DEFAULT_ENVIRONMENT;
|
|
37
|
+
// Look up in file
|
|
38
|
+
const envConfig = file?.environments[name];
|
|
39
|
+
if (envConfig) {
|
|
40
|
+
return {
|
|
41
|
+
name,
|
|
42
|
+
appURL: envConfig.appURL,
|
|
43
|
+
apiURL: envConfig.apiURL,
|
|
44
|
+
services: envConfig.services ?? {},
|
|
45
|
+
credentials: envConfig.credentials ?? null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Environment not in file — use production defaults or error
|
|
49
|
+
if (name === DEFAULT_ENVIRONMENT) {
|
|
50
|
+
return {
|
|
51
|
+
name,
|
|
52
|
+
appURL: DEFAULT_APP_URL,
|
|
53
|
+
apiURL: DEFAULT_API_URL,
|
|
54
|
+
services: {},
|
|
55
|
+
credentials: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Unknown environment "${name}". Define it in ~/.sapiom/credentials.json:\n` +
|
|
59
|
+
JSON.stringify({
|
|
60
|
+
currentEnvironment: name,
|
|
61
|
+
environments: {
|
|
62
|
+
[name]: {
|
|
63
|
+
appURL: "http://localhost:2999",
|
|
64
|
+
apiURL: "http://localhost:3000",
|
|
65
|
+
services: {
|
|
66
|
+
prelude: "http://localhost:3002",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}, null, 2));
|
|
71
|
+
}
|
|
72
|
+
export async function readCredentials(envName) {
|
|
73
|
+
const file = await readCredentialsFile();
|
|
74
|
+
return file?.environments[envName]?.credentials ?? null;
|
|
75
|
+
}
|
|
76
|
+
export async function writeCredentials(envName, appURL, apiURL, entry) {
|
|
77
|
+
const existing = (await readCredentialsFile()) ?? {
|
|
78
|
+
currentEnvironment: envName,
|
|
79
|
+
environments: {},
|
|
80
|
+
};
|
|
81
|
+
existing.currentEnvironment = envName;
|
|
82
|
+
existing.environments[envName] = {
|
|
83
|
+
...existing.environments[envName],
|
|
84
|
+
appURL,
|
|
85
|
+
apiURL,
|
|
86
|
+
credentials: entry,
|
|
87
|
+
};
|
|
88
|
+
await writeCredentialsFile(existing);
|
|
89
|
+
}
|
|
90
|
+
export async function clearCredentials(envName) {
|
|
91
|
+
const existing = await readCredentialsFile();
|
|
92
|
+
if (!existing?.environments[envName]?.credentials)
|
|
93
|
+
return;
|
|
94
|
+
delete existing.environments[envName].credentials;
|
|
95
|
+
await writeCredentialsFile(existing);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { resolveEnvironment } from "./credentials.js";
|
|
5
|
+
import { register as registerAuthenticate } from "./tools/authenticate.js";
|
|
6
|
+
import { register as registerStatus } from "./tools/status.js";
|
|
7
|
+
import { register as registerVerify } from "./tools/verify.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
// Resolve environment: SAPIOM_ENVIRONMENT env var > file > "production"
|
|
10
|
+
const env = await resolveEnvironment(process.env.SAPIOM_ENVIRONMENT);
|
|
11
|
+
if (env.name !== "production") {
|
|
12
|
+
console.error(`\u26a0 Using environment "${env.name}": app=${env.appURL} api=${env.apiURL}`);
|
|
13
|
+
}
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "sapiom",
|
|
16
|
+
version: "0.1.0",
|
|
17
|
+
});
|
|
18
|
+
// Register all tools
|
|
19
|
+
registerAuthenticate(server, env);
|
|
20
|
+
registerStatus(server, env);
|
|
21
|
+
registerVerify(server, env);
|
|
22
|
+
// Connect via stdio transport
|
|
23
|
+
const transport = new StdioServerTransport();
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
console.error("Sapiom MCP server running on stdio");
|
|
26
|
+
}
|
|
27
|
+
main().catch((err) => {
|
|
28
|
+
console.error("Fatal error:", err);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,QAAQ,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE/D,KAAK,UAAU,IAAI;IACjB,wEAAwE;IACxE,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAErE,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CACX,6BAA6B,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,qBAAqB;IACrB,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE5B,8BAA8B;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;AACtD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../src/tools/authenticate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAsD1E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readCredentials, writeCredentials, } from "../credentials.js";
|
|
2
|
+
import { performBrowserAuth } from "../auth.js";
|
|
3
|
+
export function register(server, env) {
|
|
4
|
+
server.tool("sapiom_authenticate", "Authenticate with Sapiom by opening a browser login flow. Run this when other Sapiom tools report that authentication is required.", {}, async () => {
|
|
5
|
+
// Check if already authenticated
|
|
6
|
+
const existing = await readCredentials(env.name);
|
|
7
|
+
if (existing) {
|
|
8
|
+
return {
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: `Already authenticated as ${existing.organizationName} (tenant: ${existing.tenantId}). To re-authenticate, use sapiom_logout first.`,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const result = await performBrowserAuth(env.appURL, env.apiURL);
|
|
19
|
+
await writeCredentials(env.name, env.appURL, env.apiURL, {
|
|
20
|
+
apiKey: result.apiKey,
|
|
21
|
+
tenantId: result.tenantId,
|
|
22
|
+
organizationName: result.organizationName,
|
|
23
|
+
apiKeyId: result.apiKeyId,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `Successfully authenticated as ${result.organizationName}. Sapiom tools are now ready to use.`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const message = err instanceof Error
|
|
36
|
+
? err.message
|
|
37
|
+
: "Unknown error during authentication";
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Authentication failed: ${message}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=authenticate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticate.js","sourceRoot":"","sources":["../../src/tools/authenticate.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,GAAwB;IAClE,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,oIAAoI,EACpI,EAAE,EACF,KAAK,IAAI,EAAE;QACT,iCAAiC;QACjC,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,4BAA4B,QAAQ,CAAC,gBAAgB,aAAa,QAAQ,CAAC,QAAQ,iDAAiD;qBAC3I;iBACF;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAEhE,MAAM,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;gBACvD,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,QAAQ,EAAE,MAAM,CAAC,QAAQ;aAC1B,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,iCAAiC,MAAM,CAAC,gBAAgB,sCAAsC;qBACrG;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GACX,GAAG,YAAY,KAAK;gBAClB,CAAC,CAAC,GAAG,CAAC,OAAO;gBACb,CAAC,CAAC,qCAAqC,CAAC;YAC5C,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,0BAA0B,OAAO,EAAE;qBAC1C;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,CA+C1E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readCredentials, clearCredentials, } from "../credentials.js";
|
|
2
|
+
export function register(server, env) {
|
|
3
|
+
server.tool("sapiom_status", "Check Sapiom authentication status. Returns whether you're authenticated and which organization.", {}, async () => {
|
|
4
|
+
const creds = await readCredentials(env.name);
|
|
5
|
+
if (creds) {
|
|
6
|
+
return {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
type: "text",
|
|
10
|
+
text: `Authenticated as ${creds.organizationName} (tenant: ${creds.tenantId})`,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: "Not authenticated. Use the sapiom_authenticate tool to log in.",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
server.tool("sapiom_logout", "Log out of Sapiom by removing cached credentials for the current environment.", {}, async () => {
|
|
25
|
+
await clearCredentials(env.name);
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: "Logged out successfully. Cached credentials have been removed.",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,GAAwB;IAClE,MAAM,CAAC,IAAI,CACT,eAAe,EACf,kGAAkG,EAClG,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE9C,IAAI,KAAK,EAAE,CAAC;YACV,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,oBAAoB,KAAK,CAAC,gBAAgB,aAAa,KAAK,CAAC,QAAQ,GAAG;qBAC/E;iBACF;aACF,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,gEAAgE;iBACvE;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,+EAA+E,EAC/E,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEjC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,gEAAgE;iBACvE;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/tools/verify.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAkB9E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,CA8K1E"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createFetch } from "@sapiom/fetch";
|
|
3
|
+
import { readCredentials } from "../credentials.js";
|
|
4
|
+
const DEFAULT_PRELUDE_URL = "https://prelude.services.sapiom.ai";
|
|
5
|
+
async function getAuthenticatedFetch(env) {
|
|
6
|
+
const creds = await readCredentials(env.name);
|
|
7
|
+
if (!creds)
|
|
8
|
+
return null;
|
|
9
|
+
return createFetch({
|
|
10
|
+
apiKey: creds.apiKey,
|
|
11
|
+
baseURL: env.apiURL,
|
|
12
|
+
agentName: "sapiom-mcp",
|
|
13
|
+
integration: { name: "@sapiom/mcp", version: "0.1.0" },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function register(server, env) {
|
|
17
|
+
const preludeURL = env.services.prelude ?? DEFAULT_PRELUDE_URL;
|
|
18
|
+
server.tool("sapiom_verify_send", "Send a verification code to a phone number via SMS. Returns a verification ID that you'll need to check the code later. Phone number must be in E.164 format (e.g. +15551234567).", {
|
|
19
|
+
phoneNumber: z
|
|
20
|
+
.string()
|
|
21
|
+
.regex(/^\+[1-9]\d{1,14}$/, "Phone number must be in E.164 format (e.g. +15551234567)")
|
|
22
|
+
.describe("Phone number in E.164 format with country code (e.g. +15551234567)"),
|
|
23
|
+
}, async ({ phoneNumber }) => {
|
|
24
|
+
const sfetch = await getAuthenticatedFetch(env);
|
|
25
|
+
if (!sfetch) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: "Not authenticated. Use the sapiom_authenticate tool first.",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
isError: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const response = await sfetch(`${preludeURL}/verifications`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" },
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
target: {
|
|
42
|
+
type: "phone_number",
|
|
43
|
+
value: phoneNumber,
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const body = (await response.json().catch(() => ({})));
|
|
49
|
+
const message = body.message ??
|
|
50
|
+
`Failed to send verification code (${response.status})`;
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: message }],
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const data = (await response.json());
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Verification code sent to ${phoneNumber}. Verification ID: ${data.id}\n\nAsk the user for the 6-digit code they received, then use sapiom_verify_check to verify it.`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Failed to send verification code: ${message}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.tool("sapiom_verify_check", "Check a verification code that was sent via sapiom_verify_send. Returns whether the code is correct.", {
|
|
80
|
+
verificationId: z
|
|
81
|
+
.string()
|
|
82
|
+
.describe("The verification ID returned by sapiom_verify_send"),
|
|
83
|
+
code: z
|
|
84
|
+
.string()
|
|
85
|
+
.regex(/^\d{6}$/, "Code must be exactly 6 digits")
|
|
86
|
+
.describe("The 6-digit verification code the user received"),
|
|
87
|
+
}, async ({ verificationId, code }) => {
|
|
88
|
+
const sfetch = await getAuthenticatedFetch(env);
|
|
89
|
+
if (!sfetch) {
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: "Not authenticated. Use the sapiom_authenticate tool first.",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const response = await sfetch(`${preludeURL}/verifications/check`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
verificationRequestId: verificationId,
|
|
106
|
+
code,
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const body = (await response.json().catch(() => ({})));
|
|
111
|
+
const message = body.message ??
|
|
112
|
+
`Verification check failed (${response.status})`;
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: "text", text: message }],
|
|
115
|
+
isError: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const data = (await response.json());
|
|
119
|
+
if (data.status === "success") {
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: "Verification successful! The code is correct.",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: `Verification ${data.status}. The code may be incorrect or expired.`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: `Verification check failed: ${message}`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/tools/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sapiom/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Sapiom MCP server for Claude Code — browser-based authentication and API tools",
|
|
5
|
+
"mcpName": "io.github.sapiom/mcp",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"sapiom",
|
|
8
|
+
"mcp",
|
|
9
|
+
"claude",
|
|
10
|
+
"authentication",
|
|
11
|
+
"api-keys"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/sapiom/sapiom-js/tree/main/packages/mcp#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/sapiom/sapiom-js/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/sapiom/sapiom-js.git",
|
|
20
|
+
"directory": "packages/mcp"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Sapiom",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"bin": {
|
|
28
|
+
"sapiom-mcp": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
35
|
+
"zod": "^3.25.0",
|
|
36
|
+
"@sapiom/core": "0.3.0",
|
|
37
|
+
"@sapiom/fetch": "0.3.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.11.30",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
|
42
|
+
"@typescript-eslint/parser": "^7.3.1",
|
|
43
|
+
"eslint": "^8.57.0",
|
|
44
|
+
"prettier": "^3.2.5",
|
|
45
|
+
"typescript": "^5.4.2",
|
|
46
|
+
"vitest": "^3.0.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc --project tsconfig.build.json",
|
|
56
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
57
|
+
"dev": "tsc --watch",
|
|
58
|
+
"start": "node dist/index.js",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:coverage": "vitest run --coverage",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"lint": "eslint src --ext .ts",
|
|
64
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
65
|
+
}
|
|
66
|
+
}
|