@plaud-ai/mcp 0.2.2 → 0.2.3
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/dist/{chunk-SNSGVRCU.js → chunk-3XRFIJUG.js} +119 -1
- package/dist/{chunk-IZKXHQM3.js → chunk-BZOIX6WC.js} +31 -1
- package/dist/{chunk-7KGB7GSZ.js → chunk-GZ7QPRKV.js} +1 -1
- package/dist/index.js +46 -76
- package/dist/{install-EEQXUUG3.js → install-C7WMVIPG.js} +38 -58
- package/dist/server-ORMBAVW6.js +1039 -0
- package/package.json +12 -11
- package/plugin.json +1 -1
- package/dist/server-QWAZPBHU.js +0 -293
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaud-ai/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
".mcp.json",
|
|
13
13
|
"skills"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"version:show": "node -p \"require('./package.json').version\"",
|
|
17
|
+
"prebuild": "node ../../scripts/sync-skills.mjs",
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"clean": "rm -rf dist skills",
|
|
21
|
+
"prepublishOnly": "node -e \"const fs=require('fs'),v=require('./package.json').version,p=JSON.parse(fs.readFileSync('./plugin.json','utf8'));p.version=v;fs.writeFileSync('./plugin.json',JSON.stringify(p,null,2)+'\\n');\""
|
|
22
|
+
},
|
|
15
23
|
"dependencies": {
|
|
16
24
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
17
25
|
"express": "^5.2.1",
|
|
@@ -20,16 +28,9 @@
|
|
|
20
28
|
"zod": "^4.3.6"
|
|
21
29
|
},
|
|
22
30
|
"devDependencies": {
|
|
31
|
+
"@plaud-ai/shared": "workspace:*",
|
|
23
32
|
"@types/express": "^5.0.6",
|
|
24
33
|
"@types/node": "^25.5.0",
|
|
25
|
-
"typescript": "^5.7.0"
|
|
26
|
-
"@plaud-ai/shared": "0.1.0"
|
|
27
|
-
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"version:show": "node -p \"require('./package.json').version\"",
|
|
30
|
-
"prebuild": "node ../../scripts/sync-skills.mjs",
|
|
31
|
-
"build": "tsup",
|
|
32
|
-
"dev": "tsup --watch",
|
|
33
|
-
"clean": "rm -rf dist skills"
|
|
34
|
+
"typescript": "^5.7.0"
|
|
34
35
|
}
|
|
35
|
-
}
|
|
36
|
+
}
|
package/plugin.json
CHANGED
package/dist/server-QWAZPBHU.js
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
logger,
|
|
3
|
-
registerTools
|
|
4
|
-
} from "./chunk-IZKXHQM3.js";
|
|
5
|
-
import {
|
|
6
|
-
PlaudClient
|
|
7
|
-
} from "./chunk-SNSGVRCU.js";
|
|
8
|
-
|
|
9
|
-
// src/http/server.ts
|
|
10
|
-
import express from "express";
|
|
11
|
-
import { createServer } from "http";
|
|
12
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
13
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
15
|
-
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
16
|
-
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
17
|
-
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
18
|
-
|
|
19
|
-
// src/http/oauth-provider.ts
|
|
20
|
-
import { randomUUID } from "crypto";
|
|
21
|
-
import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
|
|
22
|
-
var PlaudOAuthProvider = class extends ProxyOAuthServerProvider {
|
|
23
|
-
_plaudClientId;
|
|
24
|
-
_plaudTokenUrl;
|
|
25
|
-
_plaudApiBase;
|
|
26
|
-
_callbackUrl;
|
|
27
|
-
_registeredClients = /* @__PURE__ */ new Map();
|
|
28
|
-
// internalState → { clientRedirectUri, originalState }
|
|
29
|
-
// We generate our own state to track the pending flow regardless of whether the client sent one.
|
|
30
|
-
_pendingStates = /* @__PURE__ */ new Map();
|
|
31
|
-
constructor(options) {
|
|
32
|
-
const authUrl = options.authUrl ?? "https://web.plaud.ai/platform/oauth";
|
|
33
|
-
const tokenUrl = options.tokenUrl ?? "https://platform.plaud.ai/developer/api/oauth/third-party/access-token";
|
|
34
|
-
const apiBase = options.apiBase ?? "https://platform.plaud.ai/developer/api";
|
|
35
|
-
super({
|
|
36
|
-
endpoints: {
|
|
37
|
-
authorizationUrl: authUrl,
|
|
38
|
-
tokenUrl
|
|
39
|
-
},
|
|
40
|
-
verifyAccessToken: async (token) => {
|
|
41
|
-
const client = new PlaudClient({
|
|
42
|
-
clientId: options.clientId,
|
|
43
|
-
clientSecret: "",
|
|
44
|
-
redirectUri: "",
|
|
45
|
-
apiBase,
|
|
46
|
-
staticToken: token
|
|
47
|
-
});
|
|
48
|
-
try {
|
|
49
|
-
const user = await client.getCurrentUser();
|
|
50
|
-
let expiresAt;
|
|
51
|
-
try {
|
|
52
|
-
const payload = JSON.parse(
|
|
53
|
-
Buffer.from(token.split(".")[1], "base64url").toString()
|
|
54
|
-
);
|
|
55
|
-
expiresAt = typeof payload.exp === "number" ? payload.exp : Math.floor(Date.now() / 1e3) + 3600;
|
|
56
|
-
} catch {
|
|
57
|
-
expiresAt = Math.floor(Date.now() / 1e3) + 3600;
|
|
58
|
-
}
|
|
59
|
-
const authInfo = {
|
|
60
|
-
token,
|
|
61
|
-
clientId: String(user.id ?? "unknown"),
|
|
62
|
-
scopes: [],
|
|
63
|
-
expiresAt
|
|
64
|
-
};
|
|
65
|
-
logger.info({ event: "token_verified", client_id: authInfo.clientId, expires_at: expiresAt });
|
|
66
|
-
return authInfo;
|
|
67
|
-
} catch (err) {
|
|
68
|
-
logger.warn({ event: "token_verify_failed", error: String(err) });
|
|
69
|
-
throw new Error("Invalid or expired token");
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
getClient: async (id) => this._registeredClients.get(id)
|
|
73
|
-
});
|
|
74
|
-
this._plaudClientId = options.clientId;
|
|
75
|
-
this._plaudTokenUrl = tokenUrl;
|
|
76
|
-
this._plaudApiBase = apiBase;
|
|
77
|
-
this._callbackUrl = options.callbackUrl;
|
|
78
|
-
this.skipLocalPkceValidation = true;
|
|
79
|
-
}
|
|
80
|
-
// Override clientsStore to add in-memory dynamic client registration
|
|
81
|
-
get clientsStore() {
|
|
82
|
-
return {
|
|
83
|
-
getClient: async (id) => this._registeredClients.get(id),
|
|
84
|
-
registerClient: async (client) => {
|
|
85
|
-
const full = {
|
|
86
|
-
...client,
|
|
87
|
-
client_id: randomUUID(),
|
|
88
|
-
client_id_issued_at: Math.floor(Date.now() / 1e3)
|
|
89
|
-
};
|
|
90
|
-
this._registeredClients.set(full.client_id, full);
|
|
91
|
-
return full;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Redirect to Plaud using our own registered callback URL.
|
|
97
|
-
* Store the client's original redirect_uri keyed by state so we can forward after Plaud calls back.
|
|
98
|
-
*/
|
|
99
|
-
async authorize(_client, params, res) {
|
|
100
|
-
const internalState = randomUUID();
|
|
101
|
-
this._pendingStates.set(internalState, {
|
|
102
|
-
clientRedirectUri: params.redirectUri,
|
|
103
|
-
originalState: params.state
|
|
104
|
-
});
|
|
105
|
-
logger.info({ event: "oauth_authorize_start", internal_state: internalState, redirect_uri: params.redirectUri });
|
|
106
|
-
const targetUrl = new URL(this._endpoints.authorizationUrl);
|
|
107
|
-
const searchParams = new URLSearchParams({
|
|
108
|
-
client_id: this._plaudClientId,
|
|
109
|
-
response_type: "code",
|
|
110
|
-
redirect_uri: this._callbackUrl,
|
|
111
|
-
code_challenge: params.codeChallenge,
|
|
112
|
-
code_challenge_method: "S256",
|
|
113
|
-
state: internalState
|
|
114
|
-
// always send our internal state to Plaud
|
|
115
|
-
});
|
|
116
|
-
if (params.scopes?.length) searchParams.set("scope", params.scopes.join(" "));
|
|
117
|
-
targetUrl.search = searchParams.toString();
|
|
118
|
-
res.redirect(targetUrl.toString());
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Called when Plaud redirects to our /oauth/callback.
|
|
122
|
-
* Looks up the original client redirect_uri and forwards the code+state to it.
|
|
123
|
-
*/
|
|
124
|
-
handleCallback(code, state, res) {
|
|
125
|
-
const pending = this._pendingStates.get(state);
|
|
126
|
-
if (!pending) {
|
|
127
|
-
logger.warn({ event: "oauth_callback_unknown_state", state });
|
|
128
|
-
res.status(400).send("Unknown state \u2014 authorization request not found");
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
this._pendingStates.delete(state);
|
|
132
|
-
logger.info({ event: "oauth_callback_received", internal_state: state });
|
|
133
|
-
const target = new URL(pending.clientRedirectUri);
|
|
134
|
-
target.searchParams.set("code", code);
|
|
135
|
-
if (pending.originalState) {
|
|
136
|
-
target.searchParams.set("state", pending.originalState);
|
|
137
|
-
}
|
|
138
|
-
res.redirect(target.toString());
|
|
139
|
-
}
|
|
140
|
-
// Override to use PKCE public-client flow — no Basic auth, client_id sent in body.
|
|
141
|
-
async exchangeAuthorizationCode(_client, authorizationCode, codeVerifier, _redirectUri) {
|
|
142
|
-
const body = {
|
|
143
|
-
grant_type: "authorization_code",
|
|
144
|
-
client_id: this._plaudClientId,
|
|
145
|
-
code: authorizationCode,
|
|
146
|
-
redirect_uri: this._callbackUrl
|
|
147
|
-
};
|
|
148
|
-
if (codeVerifier) body.code_verifier = codeVerifier;
|
|
149
|
-
const fetchRes = await fetch(this._plaudTokenUrl, {
|
|
150
|
-
method: "POST",
|
|
151
|
-
headers: {
|
|
152
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
153
|
-
Accept: "application/json"
|
|
154
|
-
},
|
|
155
|
-
body: new URLSearchParams(body)
|
|
156
|
-
});
|
|
157
|
-
if (!fetchRes.ok) {
|
|
158
|
-
const text = await fetchRes.text();
|
|
159
|
-
logger.error({ event: "oauth_token_exchange_failed", status: fetchRes.status, body: text });
|
|
160
|
-
throw new Error(`Token exchange failed: ${fetchRes.status} ${text}`);
|
|
161
|
-
}
|
|
162
|
-
const data = await fetchRes.json();
|
|
163
|
-
logger.info({ event: "oauth_token_exchange_ok", has_refresh_token: !!data.refresh_token });
|
|
164
|
-
return {
|
|
165
|
-
access_token: data.access_token,
|
|
166
|
-
token_type: data.token_type ?? "Bearer",
|
|
167
|
-
refresh_token: data.refresh_token,
|
|
168
|
-
expires_in: data.expires_in
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// src/http/server.ts
|
|
174
|
-
var HTTP_PORT = Number(process.env.PLAUD_HTTP_PORT ?? 3e3);
|
|
175
|
-
var HTTP_HOST = process.env.PLAUD_HTTP_HOST ?? "0.0.0.0";
|
|
176
|
-
var CALLBACK_PORT = 8199;
|
|
177
|
-
var CALLBACK_PATH = "/auth/callback";
|
|
178
|
-
var CALLBACK_URL = process.env.PLAUD_CALLBACK_URL ?? `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
179
|
-
function startHttpServer() {
|
|
180
|
-
const clientId = process.env.PLAUD_MCP_CLIENT_ID ?? "client_37d250cb-50f8-4af1-8cd6-bc6711c5d684";
|
|
181
|
-
const apiBase = process.env.PLAUD_API_BASE;
|
|
182
|
-
const serverUrl = process.env.PLAUD_SERVER_URL ?? `http://localhost:${HTTP_PORT}`;
|
|
183
|
-
const provider = new PlaudOAuthProvider({
|
|
184
|
-
clientId,
|
|
185
|
-
callbackUrl: CALLBACK_URL,
|
|
186
|
-
authUrl: process.env.PLAUD_AUTH_URL,
|
|
187
|
-
tokenUrl: process.env.PLAUD_TOKEN_URL,
|
|
188
|
-
apiBase
|
|
189
|
-
});
|
|
190
|
-
const issuerUrl = new URL(serverUrl);
|
|
191
|
-
const app = createMcpExpressApp({ host: HTTP_HOST });
|
|
192
|
-
app.get("/health", (_req, res) => {
|
|
193
|
-
res.json({ status: "ok", uptime_s: Math.floor(process.uptime()) });
|
|
194
|
-
});
|
|
195
|
-
if (process.env.PLAUD_CALLBACK_URL) {
|
|
196
|
-
app.get(CALLBACK_PATH, (req, res) => {
|
|
197
|
-
const code = req.query["code"];
|
|
198
|
-
const state = req.query["state"];
|
|
199
|
-
if (!code || !state) {
|
|
200
|
-
logger.warn({ event: "oauth_callback_invalid", code: !!code, state: !!state });
|
|
201
|
-
res.status(400).send("Missing code or state");
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
provider.handleCallback(code, state, res);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
app.use((req, res, next) => {
|
|
208
|
-
const reqId = req.headers["x-request-id"] ?? randomUUID2();
|
|
209
|
-
res.locals["reqId"] = reqId;
|
|
210
|
-
logger.info({ event: "http_request", req_id: reqId, method: req.method, path: req.url, user_agent: req.headers["user-agent"] ?? null });
|
|
211
|
-
next();
|
|
212
|
-
});
|
|
213
|
-
app.post("/token", express.urlencoded({ extended: false }), (req, _res, next) => {
|
|
214
|
-
if (req.body?.resource && !URL.canParse(req.body.resource)) {
|
|
215
|
-
delete req.body.resource;
|
|
216
|
-
}
|
|
217
|
-
next();
|
|
218
|
-
});
|
|
219
|
-
app.use(
|
|
220
|
-
mcpAuthRouter({
|
|
221
|
-
provider,
|
|
222
|
-
issuerUrl,
|
|
223
|
-
baseUrl: issuerUrl,
|
|
224
|
-
resourceName: "Plaud MCP Server"
|
|
225
|
-
})
|
|
226
|
-
);
|
|
227
|
-
app.post(
|
|
228
|
-
"/mcp",
|
|
229
|
-
requireBearerAuth({ verifier: provider, resourceMetadataUrl: `${serverUrl}/.well-known/oauth-protected-resource` }),
|
|
230
|
-
async (req, res) => {
|
|
231
|
-
const token = req.auth.token;
|
|
232
|
-
const reqId = res.locals["reqId"] ?? randomUUID2();
|
|
233
|
-
const reqLog = logger.child({ req_id: reqId });
|
|
234
|
-
const startMs = Date.now();
|
|
235
|
-
reqLog.info({ event: "mcp_request_start", client_id: req.auth.clientId });
|
|
236
|
-
const client = new PlaudClient({
|
|
237
|
-
clientId,
|
|
238
|
-
clientSecret: "",
|
|
239
|
-
redirectUri: "",
|
|
240
|
-
apiBase,
|
|
241
|
-
staticToken: token
|
|
242
|
-
});
|
|
243
|
-
const mcpServer = new McpServer({ name: "plaud", version: "0.2.2" });
|
|
244
|
-
registerTools(mcpServer, client);
|
|
245
|
-
const transport = new StreamableHTTPServerTransport({
|
|
246
|
-
sessionIdGenerator: void 0
|
|
247
|
-
// stateless
|
|
248
|
-
});
|
|
249
|
-
try {
|
|
250
|
-
await mcpServer.connect(transport);
|
|
251
|
-
await transport.handleRequest(req, res, req.body);
|
|
252
|
-
const clientVersion = mcpServer.server.getClientVersion();
|
|
253
|
-
if (clientVersion) {
|
|
254
|
-
reqLog.info({ event: "mcp_client_info", client_name: clientVersion.name, client_version: clientVersion.version });
|
|
255
|
-
}
|
|
256
|
-
reqLog.info({ event: "mcp_request_end", duration_ms: Date.now() - startMs });
|
|
257
|
-
} catch (err) {
|
|
258
|
-
reqLog.error({ event: "mcp_request_error", error: String(err), duration_ms: Date.now() - startMs });
|
|
259
|
-
if (!res.headersSent) {
|
|
260
|
-
res.status(500).json({ error: String(err) });
|
|
261
|
-
}
|
|
262
|
-
} finally {
|
|
263
|
-
await mcpServer.close();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
);
|
|
267
|
-
const callbackApp = express();
|
|
268
|
-
callbackApp.get(CALLBACK_PATH, (req, res) => {
|
|
269
|
-
const code = req.query["code"];
|
|
270
|
-
const state = req.query["state"];
|
|
271
|
-
if (!code || !state) {
|
|
272
|
-
logger.warn({ event: "oauth_callback_invalid", code: !!code, state: !!state });
|
|
273
|
-
res.status(400).send("Missing code or state");
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
provider.handleCallback(code, state, res);
|
|
277
|
-
});
|
|
278
|
-
createServer(callbackApp).listen(CALLBACK_PORT, "localhost", () => {
|
|
279
|
-
logger.info({ event: "server_start", role: "oauth_callback", url: CALLBACK_URL });
|
|
280
|
-
});
|
|
281
|
-
app.listen(HTTP_PORT, HTTP_HOST, () => {
|
|
282
|
-
logger.info({
|
|
283
|
-
event: "server_start",
|
|
284
|
-
role: "mcp_http",
|
|
285
|
-
url: serverUrl,
|
|
286
|
-
mcp_endpoint: `${serverUrl}/mcp`,
|
|
287
|
-
oauth_metadata: `${serverUrl}/.well-known/oauth-authorization-server`
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
export {
|
|
292
|
-
startHttpServer
|
|
293
|
-
};
|