@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaud-ai/mcp",
3
- "version": "0.2.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plaud",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Access your Plaud recordings in Claude",
5
5
  "author": {
6
6
  "name": "Plaud AI"
@@ -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
- };