@getpultdev/mcp-server 0.1.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 +336 -0
- package/dist/chunk-2CFKYLYU.js +95 -0
- package/dist/chunk-2CFKYLYU.js.map +1 -0
- package/dist/chunk-SO33VXCT.js +16864 -0
- package/dist/chunk-SO33VXCT.js.map +1 -0
- package/dist/http-server-FZEWVMOZ.js +250 -0
- package/dist/http-server-FZEWVMOZ.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/server-FWHF3FVO.js +9 -0
- package/dist/server-FWHF3FVO.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-SO33VXCT.js";
|
|
5
|
+
import {
|
|
6
|
+
configure,
|
|
7
|
+
withToken
|
|
8
|
+
} from "./chunk-2CFKYLYU.js";
|
|
9
|
+
|
|
10
|
+
// src/http-server.ts
|
|
11
|
+
import express from "express";
|
|
12
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
13
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
|
+
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
15
|
+
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
16
|
+
|
|
17
|
+
// src/oauth-provider.ts
|
|
18
|
+
import { randomUUID, randomBytes } from "crypto";
|
|
19
|
+
var PultOAuthProvider = class {
|
|
20
|
+
clients = /* @__PURE__ */ new Map();
|
|
21
|
+
pendingAuths = /* @__PURE__ */ new Map();
|
|
22
|
+
authCodes = /* @__PURE__ */ new Map();
|
|
23
|
+
apiUrl;
|
|
24
|
+
port;
|
|
25
|
+
constructor(apiUrl, port) {
|
|
26
|
+
this.apiUrl = apiUrl;
|
|
27
|
+
this.port = port;
|
|
28
|
+
}
|
|
29
|
+
get clientsStore() {
|
|
30
|
+
return {
|
|
31
|
+
getClient: (id) => this.clients.get(id),
|
|
32
|
+
registerClient: (metadata) => {
|
|
33
|
+
const clientId = randomUUID();
|
|
34
|
+
const client = {
|
|
35
|
+
...metadata,
|
|
36
|
+
client_id: clientId,
|
|
37
|
+
client_id_issued_at: Math.floor(Date.now() / 1e3)
|
|
38
|
+
};
|
|
39
|
+
this.clients.set(clientId, client);
|
|
40
|
+
return client;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async authorize(client, params, res) {
|
|
45
|
+
const sessionId = randomUUID();
|
|
46
|
+
this.pendingAuths.set(sessionId, {
|
|
47
|
+
codeChallenge: params.codeChallenge,
|
|
48
|
+
redirectUri: params.redirectUri,
|
|
49
|
+
state: params.state,
|
|
50
|
+
clientId: client.client_id
|
|
51
|
+
});
|
|
52
|
+
const html = this.renderLoginPage(sessionId);
|
|
53
|
+
res.setHeader("Content-Type", "text/html");
|
|
54
|
+
res.end(html);
|
|
55
|
+
}
|
|
56
|
+
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
57
|
+
const stored = this.authCodes.get(authorizationCode);
|
|
58
|
+
if (!stored) throw new Error("Invalid authorization code");
|
|
59
|
+
return stored.codeChallenge;
|
|
60
|
+
}
|
|
61
|
+
async exchangeAuthorizationCode(_client, authorizationCode) {
|
|
62
|
+
const stored = this.authCodes.get(authorizationCode);
|
|
63
|
+
if (!stored) throw new Error("Invalid authorization code");
|
|
64
|
+
if (Date.now() > stored.expiresAt) {
|
|
65
|
+
this.authCodes.delete(authorizationCode);
|
|
66
|
+
throw new Error("Authorization code expired");
|
|
67
|
+
}
|
|
68
|
+
this.authCodes.delete(authorizationCode);
|
|
69
|
+
return {
|
|
70
|
+
access_token: stored.accessToken,
|
|
71
|
+
token_type: "Bearer",
|
|
72
|
+
refresh_token: stored.refreshToken,
|
|
73
|
+
expires_in: 86400
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async exchangeRefreshToken(_client, refreshToken) {
|
|
77
|
+
const response = await fetch(`${this.apiUrl}/auth/refresh`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) throw new Error("Token refresh failed");
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
return {
|
|
85
|
+
access_token: data["access_token"],
|
|
86
|
+
token_type: "Bearer",
|
|
87
|
+
refresh_token: data["refresh_token"],
|
|
88
|
+
expires_in: data["expires_in"]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async verifyAccessToken(token) {
|
|
92
|
+
const response = await fetch(`${this.apiUrl}/auth/me`, {
|
|
93
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) throw new Error("Invalid access token");
|
|
96
|
+
const user = await response.json();
|
|
97
|
+
return {
|
|
98
|
+
token,
|
|
99
|
+
clientId: "pult",
|
|
100
|
+
scopes: ["apps", "deploy", "db", "storage", "auth"],
|
|
101
|
+
extra: { userId: user["id"], email: user["email"] }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
handleCallback(sessionId, accessToken, refreshToken) {
|
|
105
|
+
const pending = this.pendingAuths.get(sessionId);
|
|
106
|
+
if (!pending) return null;
|
|
107
|
+
this.pendingAuths.delete(sessionId);
|
|
108
|
+
const code = randomBytes(32).toString("hex");
|
|
109
|
+
this.authCodes.set(code, {
|
|
110
|
+
clientId: pending.clientId,
|
|
111
|
+
codeChallenge: pending.codeChallenge,
|
|
112
|
+
redirectUri: pending.redirectUri,
|
|
113
|
+
accessToken,
|
|
114
|
+
refreshToken,
|
|
115
|
+
expiresAt: Date.now() + 5 * 60 * 1e3
|
|
116
|
+
});
|
|
117
|
+
const url = new URL(pending.redirectUri);
|
|
118
|
+
url.searchParams.set("code", code);
|
|
119
|
+
if (pending.state) url.searchParams.set("state", pending.state);
|
|
120
|
+
return url.toString();
|
|
121
|
+
}
|
|
122
|
+
renderLoginPage(sessionId) {
|
|
123
|
+
const callbackUrl = `http://localhost:${this.port}/oauth/callback/${sessionId}`;
|
|
124
|
+
const githubUrl = `${this.apiUrl}/auth/github?redirect_uri=${encodeURIComponent(callbackUrl)}`;
|
|
125
|
+
const googleUrl = `${this.apiUrl}/auth/google?redirect_uri=${encodeURIComponent(callbackUrl)}`;
|
|
126
|
+
return `<!DOCTYPE html>
|
|
127
|
+
<html lang="en"><head>
|
|
128
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
129
|
+
<title>Sign in to Pult</title>
|
|
130
|
+
<style>
|
|
131
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
132
|
+
body{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#0a0a0a;color:#fff}
|
|
133
|
+
.card{background:#141414;border:1px solid #262626;border-radius:16px;padding:40px;width:100%;max-width:380px}
|
|
134
|
+
h1{font-size:22px;font-weight:600;margin-bottom:6px}
|
|
135
|
+
.sub{color:#737373;font-size:14px;margin-bottom:32px}
|
|
136
|
+
.btn{display:block;width:100%;padding:11px 16px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;text-align:center;border:1px solid #262626;background:#1a1a1a;color:#e5e5e5;margin-bottom:10px;transition:background .15s}
|
|
137
|
+
.btn:hover{background:#262626}
|
|
138
|
+
.sep{display:flex;align-items:center;gap:12px;margin:24px 0;color:#525252;font-size:12px}
|
|
139
|
+
.sep::before,.sep::after{content:'';flex:1;height:1px;background:#262626}
|
|
140
|
+
input{width:100%;padding:10px 12px;border-radius:8px;border:1px solid #262626;background:#0a0a0a;color:#fff;font-size:14px;margin-bottom:10px;outline:none;transition:border-color .15s}
|
|
141
|
+
input:focus{border-color:#525252}
|
|
142
|
+
.btn-primary{background:#fff;color:#0a0a0a;border-color:#fff;font-weight:600}
|
|
143
|
+
.btn-primary:hover{background:#d4d4d4}
|
|
144
|
+
.err{color:#ef4444;font-size:13px;display:none;margin-bottom:10px}
|
|
145
|
+
.logo{font-size:28px;font-weight:700;margin-bottom:24px;letter-spacing:-0.5px}
|
|
146
|
+
</style></head>
|
|
147
|
+
<body><div class="card">
|
|
148
|
+
<div class="logo">Pult</div>
|
|
149
|
+
<h1>Sign in</h1>
|
|
150
|
+
<p class="sub">Connect your account to continue</p>
|
|
151
|
+
<a href="${githubUrl}" class="btn">Continue with GitHub</a>
|
|
152
|
+
<a href="${googleUrl}" class="btn">Continue with Google</a>
|
|
153
|
+
<div class="sep">or</div>
|
|
154
|
+
<form id="f">
|
|
155
|
+
<input type="email" name="email" placeholder="Email" required autocomplete="email">
|
|
156
|
+
<input type="password" name="password" placeholder="Password" required autocomplete="current-password">
|
|
157
|
+
<div class="err" id="e"></div>
|
|
158
|
+
<button type="submit" class="btn btn-primary">Sign in with email</button>
|
|
159
|
+
</form>
|
|
160
|
+
</div>
|
|
161
|
+
<script>
|
|
162
|
+
document.getElementById('f').addEventListener('submit',async e=>{
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
const el=document.getElementById('e'),d=new FormData(e.target);
|
|
165
|
+
el.style.display='none';
|
|
166
|
+
try{
|
|
167
|
+
const r=await fetch('${this.apiUrl}/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:d.get('email'),password:d.get('password')})});
|
|
168
|
+
const j=await r.json();
|
|
169
|
+
if(!r.ok){el.textContent=j.error||'Login failed';el.style.display='block';return}
|
|
170
|
+
window.location.href='${callbackUrl}?access_token='+encodeURIComponent(j.access_token)+'&refresh_token='+encodeURIComponent(j.refresh_token);
|
|
171
|
+
}catch(_){el.textContent='Network error';el.style.display='block'}
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
</body></html>`;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/http-server.ts
|
|
179
|
+
var API_URL = process.env["PULT_API_URL"] ?? "https://api.pult.rest";
|
|
180
|
+
var PORT = parseInt(process.env["PORT"] ?? "3456", 10);
|
|
181
|
+
function startHttpServer() {
|
|
182
|
+
configure(API_URL, "");
|
|
183
|
+
const app = express();
|
|
184
|
+
const provider = new PultOAuthProvider(API_URL, PORT);
|
|
185
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
186
|
+
app.use(mcpAuthRouter({
|
|
187
|
+
provider,
|
|
188
|
+
issuerUrl: new URL(`http://localhost:${PORT}`),
|
|
189
|
+
scopesSupported: ["apps", "deploy", "db", "storage", "auth"],
|
|
190
|
+
resourceName: "Pult MCP Server",
|
|
191
|
+
serviceDocumentationUrl: new URL("https://docs.pult.rest")
|
|
192
|
+
}));
|
|
193
|
+
app.get("/oauth/callback/:sessionId", (req, res) => {
|
|
194
|
+
const { sessionId } = req.params;
|
|
195
|
+
const accessToken = req.query["access_token"];
|
|
196
|
+
const refreshToken = req.query["refresh_token"] ?? "";
|
|
197
|
+
if (!sessionId || !accessToken) {
|
|
198
|
+
res.status(400).send("Missing parameters");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const redirectUrl = provider.handleCallback(sessionId, accessToken, refreshToken);
|
|
202
|
+
if (!redirectUrl) {
|
|
203
|
+
res.status(400).send("Invalid or expired session");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
res.redirect(redirectUrl);
|
|
207
|
+
});
|
|
208
|
+
const bearerAuth = requireBearerAuth({ verifier: provider });
|
|
209
|
+
app.all("/mcp", bearerAuth, async (req, res) => {
|
|
210
|
+
const authInfo = req.auth;
|
|
211
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
212
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
213
|
+
const session = sessions.get(sessionId);
|
|
214
|
+
await withToken(session.token, () => session.transport.handleRequest(req, res));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (req.method === "POST") {
|
|
218
|
+
const transport = new StreamableHTTPServerTransport({
|
|
219
|
+
sessionIdGenerator: () => randomUUID2(),
|
|
220
|
+
onsessioninitialized: (id) => {
|
|
221
|
+
sessions.set(id, { transport, token: authInfo.token });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
const mcpServer = createServer();
|
|
225
|
+
await mcpServer.connect(transport);
|
|
226
|
+
transport.onclose = () => {
|
|
227
|
+
const id = transport.sessionId;
|
|
228
|
+
if (id) sessions.delete(id);
|
|
229
|
+
};
|
|
230
|
+
await withToken(authInfo.token, () => transport.handleRequest(req, res));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
res.status(400).json({ error: "No active session. Send an initialize request first." });
|
|
234
|
+
});
|
|
235
|
+
app.get("/health", (_req, res) => {
|
|
236
|
+
res.json({ status: "ok", transport: "http", tools: 186 });
|
|
237
|
+
});
|
|
238
|
+
app.listen(PORT, () => {
|
|
239
|
+
process.stderr.write(`Pult MCP server listening on http://localhost:${PORT}
|
|
240
|
+
`);
|
|
241
|
+
process.stderr.write(`MCP endpoint: http://localhost:${PORT}/mcp
|
|
242
|
+
`);
|
|
243
|
+
process.stderr.write(`OAuth metadata: http://localhost:${PORT}/.well-known/oauth-protected-resource
|
|
244
|
+
`);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
startHttpServer
|
|
249
|
+
};
|
|
250
|
+
//# sourceMappingURL=http-server-FZEWVMOZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http-server.ts","../src/oauth-provider.ts"],"sourcesContent":["import express from \"express\"\nimport { randomUUID } from \"node:crypto\"\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\"\nimport { mcpAuthRouter } from \"@modelcontextprotocol/sdk/server/auth/router.js\"\nimport { requireBearerAuth } from \"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js\"\nimport { createServer } from \"./server.js\"\nimport { configure, withToken } from \"./http.js\"\nimport { PultOAuthProvider } from \"./oauth-provider.js\"\n\nconst API_URL = process.env[\"PULT_API_URL\"] ?? \"https://api.pult.rest\"\nconst PORT = parseInt(process.env[\"PORT\"] ?? \"3456\", 10)\n\ninterface Session {\n transport: StreamableHTTPServerTransport\n token: string\n}\n\nexport function startHttpServer(): void {\n configure(API_URL, \"\")\n const app = express()\n const provider = new PultOAuthProvider(API_URL, PORT)\n const sessions = new Map<string, Session>()\n\n app.use(mcpAuthRouter({\n provider,\n issuerUrl: new URL(`http://localhost:${PORT}`),\n scopesSupported: [\"apps\", \"deploy\", \"db\", \"storage\", \"auth\"],\n resourceName: \"Pult MCP Server\",\n serviceDocumentationUrl: new URL(\"https://docs.pult.rest\"),\n }))\n\n app.get(\"/oauth/callback/:sessionId\", (req, res) => {\n const { sessionId } = req.params\n const accessToken = req.query[\"access_token\"] as string | undefined\n const refreshToken = (req.query[\"refresh_token\"] as string | undefined) ?? \"\"\n\n if (!sessionId || !accessToken) {\n res.status(400).send(\"Missing parameters\")\n return\n }\n\n const redirectUrl = provider.handleCallback(sessionId, accessToken, refreshToken)\n if (!redirectUrl) {\n res.status(400).send(\"Invalid or expired session\")\n return\n }\n\n res.redirect(redirectUrl)\n })\n\n const bearerAuth = requireBearerAuth({ verifier: provider })\n\n app.all(\"/mcp\", bearerAuth, async (req, res) => {\n const authInfo = req.auth!\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined\n\n if (sessionId && sessions.has(sessionId)) {\n const session = sessions.get(sessionId)!\n await withToken(session.token, () => session.transport.handleRequest(req, res))\n return\n }\n\n if (req.method === \"POST\") {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (id) => {\n sessions.set(id, { transport, token: authInfo.token })\n },\n })\n\n const mcpServer = createServer()\n await mcpServer.connect(transport)\n\n transport.onclose = () => {\n const id = transport.sessionId\n if (id) sessions.delete(id)\n }\n\n await withToken(authInfo.token, () => transport.handleRequest(req, res))\n return\n }\n\n res.status(400).json({ error: \"No active session. Send an initialize request first.\" })\n })\n\n app.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\", transport: \"http\", tools: 186 })\n })\n\n app.listen(PORT, () => {\n process.stderr.write(`Pult MCP server listening on http://localhost:${PORT}\\n`)\n process.stderr.write(`MCP endpoint: http://localhost:${PORT}/mcp\\n`)\n process.stderr.write(`OAuth metadata: http://localhost:${PORT}/.well-known/oauth-protected-resource\\n`)\n })\n}\n","import { randomUUID, randomBytes } from \"node:crypto\"\nimport type { Response } from \"express\"\nimport type {\n OAuthServerProvider,\n AuthorizationParams,\n} from \"@modelcontextprotocol/sdk/server/auth/provider.js\"\nimport type { OAuthRegisteredClientsStore } from \"@modelcontextprotocol/sdk/server/auth/clients.js\"\nimport type { OAuthClientInformationFull, OAuthTokens } from \"@modelcontextprotocol/sdk/shared/auth.js\"\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\"\n\ninterface PendingAuth {\n codeChallenge: string\n redirectUri: string\n state?: string\n clientId: string\n}\n\ninterface StoredCode {\n clientId: string\n codeChallenge: string\n redirectUri: string\n accessToken: string\n refreshToken: string\n expiresAt: number\n}\n\nexport class PultOAuthProvider implements OAuthServerProvider {\n private clients = new Map<string, OAuthClientInformationFull>()\n private pendingAuths = new Map<string, PendingAuth>()\n private authCodes = new Map<string, StoredCode>()\n private apiUrl: string\n private port: number\n\n constructor(apiUrl: string, port: number) {\n this.apiUrl = apiUrl\n this.port = port\n }\n\n get clientsStore(): OAuthRegisteredClientsStore {\n return {\n getClient: (id: string) => this.clients.get(id),\n registerClient: (metadata: Omit<OAuthClientInformationFull, \"client_id\" | \"client_id_issued_at\">) => {\n const clientId = randomUUID()\n const client = {\n ...metadata,\n client_id: clientId,\n client_id_issued_at: Math.floor(Date.now() / 1000),\n } as OAuthClientInformationFull\n this.clients.set(clientId, client)\n return client\n },\n }\n }\n\n async authorize(\n client: OAuthClientInformationFull,\n params: AuthorizationParams,\n res: Response,\n ): Promise<void> {\n const sessionId = randomUUID()\n this.pendingAuths.set(sessionId, {\n codeChallenge: params.codeChallenge,\n redirectUri: params.redirectUri,\n state: params.state,\n clientId: client.client_id,\n })\n\n const html = this.renderLoginPage(sessionId)\n res.setHeader(\"Content-Type\", \"text/html\")\n res.end(html)\n }\n\n async challengeForAuthorizationCode(\n _client: OAuthClientInformationFull,\n authorizationCode: string,\n ): Promise<string> {\n const stored = this.authCodes.get(authorizationCode)\n if (!stored) throw new Error(\"Invalid authorization code\")\n return stored.codeChallenge\n }\n\n async exchangeAuthorizationCode(\n _client: OAuthClientInformationFull,\n authorizationCode: string,\n ): Promise<OAuthTokens> {\n const stored = this.authCodes.get(authorizationCode)\n if (!stored) throw new Error(\"Invalid authorization code\")\n if (Date.now() > stored.expiresAt) {\n this.authCodes.delete(authorizationCode)\n throw new Error(\"Authorization code expired\")\n }\n this.authCodes.delete(authorizationCode)\n return {\n access_token: stored.accessToken,\n token_type: \"Bearer\",\n refresh_token: stored.refreshToken,\n expires_in: 86400,\n }\n }\n\n async exchangeRefreshToken(\n _client: OAuthClientInformationFull,\n refreshToken: string,\n ): Promise<OAuthTokens> {\n const response = await fetch(`${this.apiUrl}/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: refreshToken }),\n })\n if (!response.ok) throw new Error(\"Token refresh failed\")\n const data = (await response.json()) as Record<string, unknown>\n return {\n access_token: data[\"access_token\"] as string,\n token_type: \"Bearer\",\n refresh_token: data[\"refresh_token\"] as string,\n expires_in: data[\"expires_in\"] as number,\n }\n }\n\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n const response = await fetch(`${this.apiUrl}/auth/me`, {\n headers: { Authorization: `Bearer ${token}` },\n })\n if (!response.ok) throw new Error(\"Invalid access token\")\n const user = (await response.json()) as Record<string, unknown>\n return {\n token,\n clientId: \"pult\",\n scopes: [\"apps\", \"deploy\", \"db\", \"storage\", \"auth\"],\n extra: { userId: user[\"id\"], email: user[\"email\"] },\n }\n }\n\n handleCallback(sessionId: string, accessToken: string, refreshToken: string): string | null {\n const pending = this.pendingAuths.get(sessionId)\n if (!pending) return null\n this.pendingAuths.delete(sessionId)\n\n const code = randomBytes(32).toString(\"hex\")\n this.authCodes.set(code, {\n clientId: pending.clientId,\n codeChallenge: pending.codeChallenge,\n redirectUri: pending.redirectUri,\n accessToken,\n refreshToken,\n expiresAt: Date.now() + 5 * 60 * 1000,\n })\n\n const url = new URL(pending.redirectUri)\n url.searchParams.set(\"code\", code)\n if (pending.state) url.searchParams.set(\"state\", pending.state)\n return url.toString()\n }\n\n private renderLoginPage(sessionId: string): string {\n const callbackUrl = `http://localhost:${this.port}/oauth/callback/${sessionId}`\n const githubUrl = `${this.apiUrl}/auth/github?redirect_uri=${encodeURIComponent(callbackUrl)}`\n const googleUrl = `${this.apiUrl}/auth/google?redirect_uri=${encodeURIComponent(callbackUrl)}`\n\n return `<!DOCTYPE html>\n<html lang=\"en\"><head>\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Sign in to Pult</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\nbody{font-family:system-ui,-apple-system,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#0a0a0a;color:#fff}\n.card{background:#141414;border:1px solid #262626;border-radius:16px;padding:40px;width:100%;max-width:380px}\nh1{font-size:22px;font-weight:600;margin-bottom:6px}\n.sub{color:#737373;font-size:14px;margin-bottom:32px}\n.btn{display:block;width:100%;padding:11px 16px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;text-align:center;border:1px solid #262626;background:#1a1a1a;color:#e5e5e5;margin-bottom:10px;transition:background .15s}\n.btn:hover{background:#262626}\n.sep{display:flex;align-items:center;gap:12px;margin:24px 0;color:#525252;font-size:12px}\n.sep::before,.sep::after{content:'';flex:1;height:1px;background:#262626}\ninput{width:100%;padding:10px 12px;border-radius:8px;border:1px solid #262626;background:#0a0a0a;color:#fff;font-size:14px;margin-bottom:10px;outline:none;transition:border-color .15s}\ninput:focus{border-color:#525252}\n.btn-primary{background:#fff;color:#0a0a0a;border-color:#fff;font-weight:600}\n.btn-primary:hover{background:#d4d4d4}\n.err{color:#ef4444;font-size:13px;display:none;margin-bottom:10px}\n.logo{font-size:28px;font-weight:700;margin-bottom:24px;letter-spacing:-0.5px}\n</style></head>\n<body><div class=\"card\">\n<div class=\"logo\">Pult</div>\n<h1>Sign in</h1>\n<p class=\"sub\">Connect your account to continue</p>\n<a href=\"${githubUrl}\" class=\"btn\">Continue with GitHub</a>\n<a href=\"${googleUrl}\" class=\"btn\">Continue with Google</a>\n<div class=\"sep\">or</div>\n<form id=\"f\">\n<input type=\"email\" name=\"email\" placeholder=\"Email\" required autocomplete=\"email\">\n<input type=\"password\" name=\"password\" placeholder=\"Password\" required autocomplete=\"current-password\">\n<div class=\"err\" id=\"e\"></div>\n<button type=\"submit\" class=\"btn btn-primary\">Sign in with email</button>\n</form>\n</div>\n<script>\ndocument.getElementById('f').addEventListener('submit',async e=>{\ne.preventDefault();\nconst el=document.getElementById('e'),d=new FormData(e.target);\nel.style.display='none';\ntry{\nconst r=await fetch('${this.apiUrl}/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:d.get('email'),password:d.get('password')})});\nconst j=await r.json();\nif(!r.ok){el.textContent=j.error||'Login failed';el.style.display='block';return}\nwindow.location.href='${callbackUrl}?access_token='+encodeURIComponent(j.access_token)+'&refresh_token='+encodeURIComponent(j.refresh_token);\n}catch(_){el.textContent='Network error';el.style.display='block'}\n});\n</script>\n</body></html>`\n }\n}\n"],"mappings":"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","names":["randomUUID","randomUUID"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configure
|
|
4
|
+
} from "./chunk-2CFKYLYU.js";
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
var transport = process.argv.includes("--http") || process.env["PULT_TRANSPORT"] === "http" ? "http" : "stdio";
|
|
8
|
+
if (transport === "http") {
|
|
9
|
+
const { startHttpServer } = await import("./http-server-FZEWVMOZ.js");
|
|
10
|
+
startHttpServer();
|
|
11
|
+
} else {
|
|
12
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
13
|
+
const { createServer } = await import("./server-FWHF3FVO.js");
|
|
14
|
+
const apiUrl = process.env["PULT_API_URL"] || "https://api.pult.rest";
|
|
15
|
+
const apiKey = process.env["PULT_API_KEY"] || "";
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
process.stderr.write("Warning: PULT_API_KEY not set. API calls will fail.\n");
|
|
18
|
+
}
|
|
19
|
+
configure(apiUrl, apiKey);
|
|
20
|
+
const server = createServer();
|
|
21
|
+
const stdioTransport = new StdioServerTransport();
|
|
22
|
+
await server.connect(stdioTransport);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { configure } from \"./http.js\"\n\nconst transport = process.argv.includes(\"--http\") || process.env[\"PULT_TRANSPORT\"] === \"http\"\n ? \"http\" : \"stdio\"\n\nif (transport === \"http\") {\n const { startHttpServer } = await import(\"./http-server.js\")\n startHttpServer()\n} else {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\")\n const { createServer } = await import(\"./server.js\")\n\n const apiUrl = process.env[\"PULT_API_URL\"] || \"https://api.pult.rest\"\n const apiKey = process.env[\"PULT_API_KEY\"] || \"\"\n\n if (!apiKey) {\n process.stderr.write(\"Warning: PULT_API_KEY not set. API calls will fail.\\n\")\n }\n\n configure(apiUrl, apiKey)\n\n const server = createServer()\n const stdioTransport = new StdioServerTransport()\n await server.connect(stdioTransport)\n}\n"],"mappings":";;;;;;AAEA,IAAM,YAAY,QAAQ,KAAK,SAAS,QAAQ,KAAK,QAAQ,IAAI,gBAAgB,MAAM,SACnF,SAAS;AAEb,IAAI,cAAc,QAAQ;AACxB,QAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,2BAAkB;AAC3D,kBAAgB;AAClB,OAAO;AACL,QAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,2CAA2C;AACzF,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,sBAAa;AAEnD,QAAM,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC9C,QAAM,SAAS,QAAQ,IAAI,cAAc,KAAK;AAE9C,MAAI,CAAC,QAAQ;AACX,YAAQ,OAAO,MAAM,uDAAuD;AAAA,EAC9E;AAEA,YAAU,QAAQ,MAAM;AAExB,QAAM,SAAS,aAAa;AAC5B,QAAM,iBAAiB,IAAI,qBAAqB;AAChD,QAAM,OAAO,QAAQ,cAAc;AACrC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getpultdev/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Pult platform — manage apps, deployments, databases, and more from AI tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pult-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"lint": "tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pult",
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"claude",
|
|
27
|
+
"cursor",
|
|
28
|
+
"ai-tools",
|
|
29
|
+
"paas",
|
|
30
|
+
"baas",
|
|
31
|
+
"deploy",
|
|
32
|
+
"database"
|
|
33
|
+
],
|
|
34
|
+
"author": "Pult <hello@pult.rest>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/getPult/pult-mcp.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://pult.rest",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
43
|
+
"cors": "^2.8.6",
|
|
44
|
+
"express": "^5.2.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/express": "^5.0.6",
|
|
48
|
+
"@types/node": "^25.3.5",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.4.0",
|
|
51
|
+
"vitest": "^2.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|