@studiometa/productive-mcp 0.10.8 → 0.10.9
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/errors.d.ts.map +1 -1
- package/dist/handlers/activities.d.ts +98 -3
- package/dist/handlers/activities.d.ts.map +1 -1
- package/dist/handlers/attachments.d.ts +98 -3
- package/dist/handlers/attachments.d.ts.map +1 -1
- package/dist/handlers/bookings.d.ts +98 -3
- package/dist/handlers/bookings.d.ts.map +1 -1
- package/dist/handlers/comments.d.ts +98 -3
- package/dist/handlers/comments.d.ts.map +1 -1
- package/dist/handlers/companies.d.ts +98 -3
- package/dist/handlers/companies.d.ts.map +1 -1
- package/dist/handlers/custom-fields.d.ts +98 -3
- package/dist/handlers/custom-fields.d.ts.map +1 -1
- package/dist/handlers/deals.d.ts +98 -3
- package/dist/handlers/deals.d.ts.map +1 -1
- package/dist/handlers/discussions.d.ts +98 -3
- package/dist/handlers/discussions.d.ts.map +1 -1
- package/dist/handlers/pages.d.ts +98 -3
- package/dist/handlers/pages.d.ts.map +1 -1
- package/dist/handlers/projects.d.ts +98 -3
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/services.d.ts +98 -3
- package/dist/handlers/services.d.ts.map +1 -1
- package/dist/handlers/tasks.d.ts +98 -3
- package/dist/handlers/tasks.d.ts.map +1 -1
- package/dist/handlers/time.d.ts +98 -3
- package/dist/handlers/time.d.ts.map +1 -1
- package/dist/handlers/timers.d.ts +98 -3
- package/dist/handlers/timers.d.ts.map +1 -1
- package/dist/http.d.ts +7 -7
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +51 -40
- package/dist/http.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/oauth.d.ts +9 -9
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +39 -39
- package/dist/oauth.js.map +1 -1
- package/dist/schema.d.ts +62 -62
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/stdio.d.ts +4 -4
- package/dist/{version-DpBFJ7eV.js → version-BFw4junA.js} +2 -2
- package/dist/{version-DpBFJ7eV.js.map → version-BFw4junA.js.map} +1 -1
- package/package.json +10 -18
package/dist/oauth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAuthToken } from "./auth.js";
|
|
2
2
|
import { createAuthCode, decodeAuthCode } from "./crypto.js";
|
|
3
|
-
import {
|
|
3
|
+
import { defineHandler, getQuery, redirect } from "h3";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
/**
|
|
6
6
|
* OAuth 2.0 endpoints for Claude Desktop integration
|
|
@@ -24,11 +24,11 @@ import { createHash } from "node:crypto";
|
|
|
24
24
|
*
|
|
25
25
|
* MCP clients MUST check this endpoint first for server capabilities.
|
|
26
26
|
*/
|
|
27
|
-
const oauthMetadataHandler =
|
|
28
|
-
const host = event.
|
|
29
|
-
const baseUrl = `${event.
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const oauthMetadataHandler = defineHandler((event) => {
|
|
28
|
+
const host = event.req.headers.get("host") || "localhost:3000";
|
|
29
|
+
const baseUrl = `${event.req.headers.get("x-forwarded-proto") || "http"}://${host}`;
|
|
30
|
+
event.res.headers.set("Content-Type", "application/json");
|
|
31
|
+
event.res.headers.set("Cache-Control", "public, max-age=3600");
|
|
32
32
|
return {
|
|
33
33
|
issuer: baseUrl,
|
|
34
34
|
authorization_endpoint: `${baseUrl}/authorize`,
|
|
@@ -50,13 +50,13 @@ const oauthMetadataHandler = defineEventHandler((event) => {
|
|
|
50
50
|
* Since we use stateless tokens, we accept any registration and return
|
|
51
51
|
* a generated client_id.
|
|
52
52
|
*/
|
|
53
|
-
const registerHandler =
|
|
54
|
-
|
|
53
|
+
const registerHandler = defineHandler(async (event) => {
|
|
54
|
+
event.res.headers.set("Content-Type", "application/json");
|
|
55
55
|
let body;
|
|
56
56
|
try {
|
|
57
|
-
body = await
|
|
57
|
+
body = await event.req.json();
|
|
58
58
|
} catch {
|
|
59
|
-
event.
|
|
59
|
+
event.res.status = 400;
|
|
60
60
|
return {
|
|
61
61
|
error: "invalid_request",
|
|
62
62
|
error_description: "Invalid JSON body"
|
|
@@ -68,7 +68,7 @@ const registerHandler = defineEventHandler(async (event) => {
|
|
|
68
68
|
name: clientName,
|
|
69
69
|
ts: Date.now()
|
|
70
70
|
})).toString("base64url");
|
|
71
|
-
event.
|
|
71
|
+
event.res.status = 201;
|
|
72
72
|
return {
|
|
73
73
|
client_id: clientId,
|
|
74
74
|
client_name: clientName,
|
|
@@ -82,7 +82,7 @@ const registerHandler = defineEventHandler(async (event) => {
|
|
|
82
82
|
* Authorization endpoint - shows login form
|
|
83
83
|
* GET /authorize
|
|
84
84
|
*/
|
|
85
|
-
const authorizeGetHandler =
|
|
85
|
+
const authorizeGetHandler = defineHandler((event) => {
|
|
86
86
|
const query = getQuery(event);
|
|
87
87
|
const clientId = query.client_id;
|
|
88
88
|
const redirectUri = query.redirect_uri;
|
|
@@ -91,8 +91,8 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
91
91
|
const codeChallengeMethod = query.code_challenge_method;
|
|
92
92
|
const scope = query.scope;
|
|
93
93
|
if (!redirectUri) {
|
|
94
|
-
|
|
95
|
-
event.
|
|
94
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
95
|
+
event.res.status = 400;
|
|
96
96
|
return renderErrorPage("Missing required parameter: redirect_uri");
|
|
97
97
|
}
|
|
98
98
|
if (!codeChallenge) {
|
|
@@ -100,16 +100,16 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
100
100
|
errorUrl.searchParams.set("error", "invalid_request");
|
|
101
101
|
errorUrl.searchParams.set("error_description", "code_challenge is required");
|
|
102
102
|
if (state) errorUrl.searchParams.set("state", state);
|
|
103
|
-
return
|
|
103
|
+
return redirect(errorUrl.toString());
|
|
104
104
|
}
|
|
105
105
|
if (codeChallengeMethod && codeChallengeMethod !== "S256") {
|
|
106
106
|
const errorUrl = new URL(redirectUri);
|
|
107
107
|
errorUrl.searchParams.set("error", "invalid_request");
|
|
108
108
|
errorUrl.searchParams.set("error_description", "Only S256 code_challenge_method is supported");
|
|
109
109
|
if (state) errorUrl.searchParams.set("state", state);
|
|
110
|
-
return
|
|
110
|
+
return redirect(errorUrl.toString());
|
|
111
111
|
}
|
|
112
|
-
|
|
112
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
113
113
|
return renderLoginForm({
|
|
114
114
|
clientId,
|
|
115
115
|
redirectUri,
|
|
@@ -123,11 +123,12 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
123
123
|
* Authorization endpoint - process login
|
|
124
124
|
* POST /authorize
|
|
125
125
|
*/
|
|
126
|
-
const authorizePostHandler =
|
|
127
|
-
const
|
|
126
|
+
const authorizePostHandler = defineHandler(async (event) => {
|
|
127
|
+
const formData = await event.req.formData();
|
|
128
|
+
const { orgId, apiToken, userId, redirectUri, state, codeChallenge, codeChallengeMethod } = Object.fromEntries(formData.entries());
|
|
128
129
|
if (!redirectUri) {
|
|
129
|
-
|
|
130
|
-
event.
|
|
130
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
131
|
+
event.res.status = 400;
|
|
131
132
|
return renderErrorPage("Missing redirect_uri parameter");
|
|
132
133
|
}
|
|
133
134
|
try {
|
|
@@ -135,15 +136,15 @@ const authorizePostHandler = defineEventHandler(async (event) => {
|
|
|
135
136
|
const isLocalhost = uri.hostname === "localhost" || uri.hostname === "127.0.0.1";
|
|
136
137
|
const isHttps = uri.protocol === "https:";
|
|
137
138
|
if (!isLocalhost && !isHttps) {
|
|
138
|
-
event.
|
|
139
|
+
event.res.status = 400;
|
|
139
140
|
return renderErrorPage("redirect_uri must be HTTPS or localhost");
|
|
140
141
|
}
|
|
141
142
|
} catch {
|
|
142
|
-
event.
|
|
143
|
+
event.res.status = 400;
|
|
143
144
|
return renderErrorPage("Invalid redirect_uri format");
|
|
144
145
|
}
|
|
145
146
|
if (!orgId || !apiToken) {
|
|
146
|
-
|
|
147
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
147
148
|
return renderLoginForm({
|
|
148
149
|
redirectUri,
|
|
149
150
|
state,
|
|
@@ -162,7 +163,7 @@ const authorizePostHandler = defineEventHandler(async (event) => {
|
|
|
162
163
|
const redirectUrl = new URL(redirectUri);
|
|
163
164
|
redirectUrl.searchParams.set("code", code);
|
|
164
165
|
if (state) redirectUrl.searchParams.set("state", state);
|
|
165
|
-
|
|
166
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
166
167
|
return renderSuccessPage(redirectUrl.toString());
|
|
167
168
|
});
|
|
168
169
|
/**
|
|
@@ -173,32 +174,31 @@ const authorizePostHandler = defineEventHandler(async (event) => {
|
|
|
173
174
|
* - authorization_code grant (with PKCE validation)
|
|
174
175
|
* - refresh_token grant
|
|
175
176
|
*/
|
|
176
|
-
const tokenHandler =
|
|
177
|
-
|
|
177
|
+
const tokenHandler = defineHandler(async (event) => {
|
|
178
|
+
event.res.headers.set("Content-Type", "application/json");
|
|
178
179
|
let body;
|
|
179
|
-
if ((event.
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
} else body = await readBody(event);
|
|
180
|
+
if ((event.req.headers.get("content-type") || "").includes("application/x-www-form-urlencoded")) {
|
|
181
|
+
const rawText = await event.req.text();
|
|
182
|
+
body = Object.fromEntries(new URLSearchParams(rawText));
|
|
183
|
+
} else body = await event.req.json();
|
|
184
184
|
const { grant_type, code, code_verifier, refresh_token } = body;
|
|
185
185
|
if (grant_type === "refresh_token") return handleRefreshToken(event, refresh_token);
|
|
186
186
|
if (grant_type !== "authorization_code") {
|
|
187
|
-
event.
|
|
187
|
+
event.res.status = 400;
|
|
188
188
|
return {
|
|
189
189
|
error: "unsupported_grant_type",
|
|
190
190
|
error_description: "Supported grant types: authorization_code, refresh_token"
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
if (!code) {
|
|
194
|
-
event.
|
|
194
|
+
event.res.status = 400;
|
|
195
195
|
return {
|
|
196
196
|
error: "invalid_request",
|
|
197
197
|
error_description: "Missing authorization code"
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
if (!code_verifier) {
|
|
201
|
-
event.
|
|
201
|
+
event.res.status = 400;
|
|
202
202
|
return {
|
|
203
203
|
error: "invalid_request",
|
|
204
204
|
error_description: "Missing code_verifier (PKCE required)"
|
|
@@ -208,7 +208,7 @@ const tokenHandler = defineEventHandler(async (event) => {
|
|
|
208
208
|
const payload = decodeAuthCode(code);
|
|
209
209
|
if (payload.codeChallenge) {
|
|
210
210
|
if (createS256Challenge(code_verifier) !== payload.codeChallenge) {
|
|
211
|
-
event.
|
|
211
|
+
event.res.status = 400;
|
|
212
212
|
return {
|
|
213
213
|
error: "invalid_grant",
|
|
214
214
|
error_description: "Invalid code_verifier"
|
|
@@ -230,7 +230,7 @@ const tokenHandler = defineEventHandler(async (event) => {
|
|
|
230
230
|
}, 86400 * 30)
|
|
231
231
|
};
|
|
232
232
|
} catch (error) {
|
|
233
|
-
event.
|
|
233
|
+
event.res.status = 400;
|
|
234
234
|
return {
|
|
235
235
|
error: "invalid_grant",
|
|
236
236
|
error_description: error instanceof Error ? error.message : "Invalid authorization code"
|
|
@@ -242,7 +242,7 @@ const tokenHandler = defineEventHandler(async (event) => {
|
|
|
242
242
|
*/
|
|
243
243
|
function handleRefreshToken(event, refreshToken) {
|
|
244
244
|
if (!refreshToken) {
|
|
245
|
-
event.
|
|
245
|
+
event.res.status = 400;
|
|
246
246
|
return {
|
|
247
247
|
error: "invalid_request",
|
|
248
248
|
error_description: "Missing refresh_token"
|
|
@@ -265,7 +265,7 @@ function handleRefreshToken(event, refreshToken) {
|
|
|
265
265
|
}, 86400 * 30)
|
|
266
266
|
};
|
|
267
267
|
} catch (error) {
|
|
268
|
-
event.
|
|
268
|
+
event.res.status = 400;
|
|
269
269
|
return {
|
|
270
270
|
error: "invalid_grant",
|
|
271
271
|
error_description: error instanceof Error ? error.message : "Invalid refresh token"
|
package/dist/oauth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","names":[],"sources":["../src/oauth.ts"],"sourcesContent":["/**\n * OAuth 2.0 endpoints for Claude Desktop integration\n *\n * Implements OAuth 2.1 with PKCE as specified in the MCP authorization spec.\n * Uses stateless encrypted tokens - no server-side storage required.\n *\n * Spec: https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization\n *\n * Flow:\n * 1. Claude redirects user to /authorize with OAuth params (including PKCE)\n * 2. User enters Productive credentials in login form\n * 3. Server encrypts credentials + PKCE challenge into authorization code\n * 4. Redirects back to Claude with the code\n * 5. Claude exchanges code for access token via /token (with code_verifier)\n * 6. Server validates PKCE and returns access token\n */\n\nimport {\n defineEventHandler,\n getQuery,\n readBody,\n sendRedirect,\n setResponseHeader,\n type H3Event,\n} from 'h3';\nimport { createHash } from 'node:crypto';\n\nimport { createAuthToken } from './auth.js';\nimport { createAuthCode, decodeAuthCode } from './crypto.js';\n\n/**\n * OAuth metadata for discovery (RFC 8414)\n * GET /.well-known/oauth-authorization-server\n *\n * MCP clients MUST check this endpoint first for server capabilities.\n */\nexport const oauthMetadataHandler = defineEventHandler((event: H3Event) => {\n const host = event.node.req.headers.host || 'localhost:3000';\n const protocol = event.node.req.headers['x-forwarded-proto'] || 'http';\n const baseUrl = `${protocol}://${host}`;\n\n setResponseHeader(event, 'Content-Type', 'application/json');\n setResponseHeader(event, 'Cache-Control', 'public, max-age=3600');\n\n return {\n // Required fields per RFC 8414\n issuer: baseUrl,\n authorization_endpoint: `${baseUrl}/authorize`,\n token_endpoint: `${baseUrl}/token`,\n response_types_supported: ['code'],\n\n // OAuth 2.1 / MCP requirements\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none'], // Public client\n\n // Optional but useful\n registration_endpoint: `${baseUrl}/register`,\n scopes_supported: ['productive'],\n service_documentation: 'https://github.com/studiometa/productive-tools',\n };\n});\n\n/**\n * Dynamic Client Registration endpoint (RFC 7591)\n * POST /register\n *\n * MCP servers SHOULD support DCR to allow clients to register automatically.\n * Since we use stateless tokens, we accept any registration and return\n * a generated client_id.\n */\nexport const registerHandler = defineEventHandler(async (event: H3Event) => {\n setResponseHeader(event, 'Content-Type', 'application/json');\n\n let body: Record<string, unknown>;\n try {\n body = await readBody(event);\n } catch {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_request',\n error_description: 'Invalid JSON body',\n };\n }\n\n // Extract client metadata\n const clientName = (body.client_name as string) || 'MCP Client';\n const redirectUris = (body.redirect_uris as string[]) || [];\n\n // Generate a client_id based on the registration\n // Since we're stateless, we encode minimal info in the client_id\n const clientId = Buffer.from(\n JSON.stringify({\n name: clientName,\n ts: Date.now(),\n }),\n ).toString('base64url');\n\n event.node.res.statusCode = 201;\n return {\n client_id: clientId,\n client_name: clientName,\n redirect_uris: redirectUris,\n token_endpoint_auth_method: 'none',\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n };\n});\n\n/**\n * Authorization endpoint - shows login form\n * GET /authorize\n */\nexport const authorizeGetHandler = defineEventHandler((event: H3Event) => {\n const query = getQuery(event);\n\n // Extract OAuth parameters\n const clientId = query.client_id as string;\n const redirectUri = query.redirect_uri as string;\n const state = query.state as string;\n const codeChallenge = query.code_challenge as string;\n const codeChallengeMethod = query.code_challenge_method as string;\n const scope = query.scope as string;\n\n // Validate required parameters per OAuth 2.1\n if (!redirectUri) {\n setResponseHeader(event, 'Content-Type', 'text/html; charset=utf-8');\n event.node.res.statusCode = 400;\n return renderErrorPage('Missing required parameter: redirect_uri');\n }\n\n // PKCE is REQUIRED for public clients per MCP spec\n if (!codeChallenge) {\n // Redirect back with error per OAuth spec\n const errorUrl = new URL(redirectUri);\n errorUrl.searchParams.set('error', 'invalid_request');\n errorUrl.searchParams.set('error_description', 'code_challenge is required');\n if (state) errorUrl.searchParams.set('state', state);\n return sendRedirect(event, errorUrl.toString());\n }\n\n if (codeChallengeMethod && codeChallengeMethod !== 'S256') {\n const errorUrl = new URL(redirectUri);\n errorUrl.searchParams.set('error', 'invalid_request');\n errorUrl.searchParams.set('error_description', 'Only S256 code_challenge_method is supported');\n if (state) errorUrl.searchParams.set('state', state);\n return sendRedirect(event, errorUrl.toString());\n }\n\n setResponseHeader(event, 'Content-Type', 'text/html; charset=utf-8');\n\n // Render login form\n return renderLoginForm({\n clientId,\n redirectUri,\n state,\n codeChallenge,\n codeChallengeMethod: codeChallengeMethod || 'S256',\n scope,\n });\n});\n\n/**\n * Authorization endpoint - process login\n * POST /authorize\n */\nexport const authorizePostHandler = defineEventHandler(async (event: H3Event) => {\n const body = await readBody(event);\n\n const { orgId, apiToken, userId, redirectUri, state, codeChallenge, codeChallengeMethod } = body;\n\n // Validate redirect URI first (security requirement)\n if (!redirectUri) {\n setResponseHeader(event, 'Content-Type', 'text/html; charset=utf-8');\n event.node.res.statusCode = 400;\n return renderErrorPage('Missing redirect_uri parameter');\n }\n\n // Validate redirect URI format (must be HTTPS or localhost)\n try {\n const uri = new URL(redirectUri);\n const isLocalhost = uri.hostname === 'localhost' || uri.hostname === '127.0.0.1';\n const isHttps = uri.protocol === 'https:';\n if (!isLocalhost && !isHttps) {\n event.node.res.statusCode = 400;\n return renderErrorPage('redirect_uri must be HTTPS or localhost');\n }\n } catch {\n event.node.res.statusCode = 400;\n return renderErrorPage('Invalid redirect_uri format');\n }\n\n // Validate required credentials\n if (!orgId || !apiToken) {\n setResponseHeader(event, 'Content-Type', 'text/html; charset=utf-8');\n return renderLoginForm({\n redirectUri,\n state,\n codeChallenge,\n codeChallengeMethod,\n error: 'Organization ID and API Token are required',\n });\n }\n\n // Create encrypted authorization code with PKCE challenge\n const code = createAuthCode({\n orgId,\n apiToken,\n userId: userId || undefined,\n codeChallenge,\n codeChallengeMethod: codeChallengeMethod || 'S256',\n });\n\n // Build redirect URL with authorization code\n const redirectUrl = new URL(redirectUri);\n redirectUrl.searchParams.set('code', code);\n if (state) {\n redirectUrl.searchParams.set('state', state);\n }\n\n // Show success page with auto-redirect\n setResponseHeader(event, 'Content-Type', 'text/html; charset=utf-8');\n return renderSuccessPage(redirectUrl.toString());\n});\n\n/**\n * Token endpoint - exchange code for access token\n * POST /token\n *\n * Supports:\n * - authorization_code grant (with PKCE validation)\n * - refresh_token grant\n */\nexport const tokenHandler = defineEventHandler(async (event: H3Event) => {\n setResponseHeader(event, 'Content-Type', 'application/json');\n\n let body: Record<string, string>;\n const contentType = event.node.req.headers['content-type'] || '';\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const rawBody = await readBody(event);\n if (typeof rawBody === 'string') {\n body = Object.fromEntries(new URLSearchParams(rawBody));\n } else {\n body = rawBody;\n }\n } else {\n body = await readBody(event);\n }\n\n const { grant_type, code, code_verifier, refresh_token } = body;\n\n // Handle refresh token grant\n if (grant_type === 'refresh_token') {\n return handleRefreshToken(event, refresh_token);\n }\n\n // Validate authorization code grant\n if (grant_type !== 'authorization_code') {\n event.node.res.statusCode = 400;\n return {\n error: 'unsupported_grant_type',\n error_description: 'Supported grant types: authorization_code, refresh_token',\n };\n }\n\n if (!code) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing authorization code',\n };\n }\n\n if (!code_verifier) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing code_verifier (PKCE required)',\n };\n }\n\n try {\n // Decode the authorization code\n const payload = decodeAuthCode(code);\n\n // Validate PKCE: SHA256(code_verifier) must equal code_challenge\n if (payload.codeChallenge) {\n const expectedChallenge = createS256Challenge(code_verifier);\n if (expectedChallenge !== payload.codeChallenge) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_grant',\n error_description: 'Invalid code_verifier',\n };\n }\n }\n\n // Create access token (base64 encoded credentials)\n const accessToken = createAuthToken({\n organizationId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n });\n\n // Create refresh token (encrypted credentials, longer expiry)\n const refreshToken = createAuthCode(\n {\n orgId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n },\n 86400 * 30, // 30 days\n );\n\n return {\n access_token: accessToken,\n token_type: 'Bearer',\n expires_in: 3600, // 1 hour (access tokens should be short-lived)\n refresh_token: refreshToken,\n };\n } catch (error) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_grant',\n error_description: error instanceof Error ? error.message : 'Invalid authorization code',\n };\n }\n});\n\n/**\n * Handle refresh token grant\n */\nfunction handleRefreshToken(event: H3Event, refreshToken: string | undefined) {\n if (!refreshToken) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing refresh_token',\n };\n }\n\n try {\n // Decode refresh token (it's just an encrypted auth code with longer expiry)\n const payload = decodeAuthCode(refreshToken);\n\n // Create new access token\n const accessToken = createAuthToken({\n organizationId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n });\n\n // Create new refresh token (rotate for security)\n const newRefreshToken = createAuthCode(\n {\n orgId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n },\n 86400 * 30, // 30 days\n );\n\n return {\n access_token: accessToken,\n token_type: 'Bearer',\n expires_in: 3600,\n refresh_token: newRefreshToken,\n };\n } catch (error) {\n event.node.res.statusCode = 400;\n return {\n error: 'invalid_grant',\n error_description: error instanceof Error ? error.message : 'Invalid refresh token',\n };\n }\n}\n\n/**\n * Create S256 PKCE challenge from verifier\n * SHA256(code_verifier) encoded as base64url\n */\nfunction createS256Challenge(codeVerifier: string): string {\n return createHash('sha256').update(codeVerifier).digest('base64url');\n}\n\n/**\n * Render the login form HTML\n */\nfunction renderLoginForm(params: {\n clientId?: string;\n redirectUri?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n scope?: string;\n error?: string;\n}): string {\n const { redirectUri, state, codeChallenge, codeChallengeMethod, error } = params;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Connect to Productive.io</title>\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n padding: 40px;\n width: 100%;\n max-width: 420px;\n }\n .logo {\n text-align: center;\n margin-bottom: 24px;\n }\n .logo svg {\n width: 48px;\n height: 48px;\n }\n h1 {\n text-align: center;\n color: #1a1a2e;\n font-size: 24px;\n margin-bottom: 8px;\n }\n .subtitle {\n text-align: center;\n color: #666;\n font-size: 14px;\n margin-bottom: 32px;\n }\n .error {\n background: #fee2e2;\n border: 1px solid #fecaca;\n color: #dc2626;\n padding: 12px 16px;\n border-radius: 8px;\n margin-bottom: 24px;\n font-size: 14px;\n }\n .form-group {\n margin-bottom: 20px;\n }\n label {\n display: block;\n font-size: 14px;\n font-weight: 500;\n color: #374151;\n margin-bottom: 6px;\n }\n input {\n width: 100%;\n padding: 12px 16px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n font-size: 16px;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n input:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);\n }\n input::placeholder {\n color: #9ca3af;\n }\n .help-text {\n font-size: 12px;\n color: #6b7280;\n margin-top: 4px;\n }\n button {\n width: 100%;\n padding: 14px 24px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n button:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n }\n button:active {\n transform: translateY(0);\n }\n .footer {\n text-align: center;\n margin-top: 24px;\n font-size: 12px;\n color: #9ca3af;\n }\n .footer a {\n color: #667eea;\n text-decoration: none;\n }\n .footer a:hover {\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"logo\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 2L2 7L12 12L22 7L12 2Z\" stroke=\"#667eea\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M2 17L12 22L22 17\" stroke=\"#764ba2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M2 12L12 17L22 12\" stroke=\"#667eea\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <h1>Connect to Productive.io</h1>\n <p class=\"subtitle\">Enter your Productive.io credentials to connect with Claude</p>\n \n ${error ? `<div class=\"error\">${escapeHtml(error)}</div>` : ''}\n \n <form method=\"POST\" action=\"/authorize\">\n <input type=\"hidden\" name=\"redirectUri\" value=\"${escapeHtml(redirectUri || '')}\">\n <input type=\"hidden\" name=\"state\" value=\"${escapeHtml(state || '')}\">\n <input type=\"hidden\" name=\"codeChallenge\" value=\"${escapeHtml(codeChallenge || '')}\">\n <input type=\"hidden\" name=\"codeChallengeMethod\" value=\"${escapeHtml(codeChallengeMethod || 'S256')}\">\n \n <div class=\"form-group\">\n <label for=\"orgId\">Organization ID *</label>\n <input type=\"text\" id=\"orgId\" name=\"orgId\" required placeholder=\"e.g., 12345\">\n <p class=\"help-text\">Found in Settings → API integrations</p>\n </div>\n \n <div class=\"form-group\">\n <label for=\"apiToken\">API Token *</label>\n <input type=\"password\" id=\"apiToken\" name=\"apiToken\" required placeholder=\"pk_...\">\n <p class=\"help-text\">Generate at Settings → API integrations → Generate new token</p>\n </div>\n \n <div class=\"form-group\">\n <label for=\"userId\">User ID (optional)</label>\n <input type=\"text\" id=\"userId\" name=\"userId\" placeholder=\"e.g., 67890\">\n <p class=\"help-text\">Required for creating time entries. Found in your profile URL.</p>\n </div>\n \n <button type=\"submit\">Connect to Productive</button>\n </form>\n \n <p class=\"footer\">\n Your credentials are encrypted and sent directly to Claude.<br>\n <a href=\"https://developer.productive.io\" target=\"_blank\">Productive.io API Documentation</a>\n </p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Render success page with auto-redirect\n */\nfunction renderSuccessPage(redirectUrl: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"refresh\" content=\"2;url=${escapeHtml(redirectUrl)}\">\n <title>Connected - Productive MCP</title>\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n padding: 40px;\n width: 100%;\n max-width: 420px;\n text-align: center;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin: 0 auto 24px;\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: white;\n }\n h1 {\n color: #1a1a2e;\n font-size: 24px;\n margin-bottom: 8px;\n }\n .message {\n color: #666;\n font-size: 14px;\n margin-bottom: 24px;\n }\n .spinner {\n width: 24px;\n height: 24px;\n border: 3px solid #e5e7eb;\n border-top-color: #667eea;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n margin: 0 auto 16px;\n }\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n .redirect-text {\n color: #9ca3af;\n font-size: 13px;\n margin-bottom: 16px;\n }\n .manual-link {\n color: #667eea;\n text-decoration: none;\n font-size: 14px;\n }\n .manual-link:hover {\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"success-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 6L9 17L4 12\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <h1>Successfully Connected!</h1>\n <p class=\"message\">Your Productive.io credentials have been verified.</p>\n <div class=\"spinner\"></div>\n <p class=\"redirect-text\">Redirecting to Claude Desktop...</p>\n <a href=\"${escapeHtml(redirectUrl)}\" class=\"manual-link\">Click here if not redirected automatically</a>\n </div>\n <script>\n // Redirect after a short delay (backup for meta refresh)\n setTimeout(function() {\n window.location.href = ${JSON.stringify(redirectUrl)};\n }, 2000);\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Render error page\n */\nfunction renderErrorPage(message: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Error - Productive MCP</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f3f4f6;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n padding: 40px;\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #dc2626;\n margin-bottom: 16px;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Error</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth.js","names":[],"sources":["../src/oauth.ts"],"sourcesContent":["/**\n * OAuth 2.0 endpoints for Claude Desktop integration\n *\n * Implements OAuth 2.1 with PKCE as specified in the MCP authorization spec.\n * Uses stateless encrypted tokens - no server-side storage required.\n *\n * Spec: https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization\n *\n * Flow:\n * 1. Claude redirects user to /authorize with OAuth params (including PKCE)\n * 2. User enters Productive credentials in login form\n * 3. Server encrypts credentials + PKCE challenge into authorization code\n * 4. Redirects back to Claude with the code\n * 5. Claude exchanges code for access token via /token (with code_verifier)\n * 6. Server validates PKCE and returns access token\n */\n\nimport { defineHandler, getQuery, redirect, type H3Event } from 'h3';\nimport { createHash } from 'node:crypto';\n\nimport { createAuthToken } from './auth.js';\nimport { createAuthCode, decodeAuthCode } from './crypto.js';\n\n/**\n * OAuth metadata for discovery (RFC 8414)\n * GET /.well-known/oauth-authorization-server\n *\n * MCP clients MUST check this endpoint first for server capabilities.\n */\nexport const oauthMetadataHandler = defineHandler((event: H3Event) => {\n const host = event.req.headers.get('host') || 'localhost:3000';\n const protocol = event.req.headers.get('x-forwarded-proto') || 'http';\n const baseUrl = `${protocol}://${host}`;\n\n event.res.headers.set('Content-Type', 'application/json');\n event.res.headers.set('Cache-Control', 'public, max-age=3600');\n\n return {\n // Required fields per RFC 8414\n issuer: baseUrl,\n authorization_endpoint: `${baseUrl}/authorize`,\n token_endpoint: `${baseUrl}/token`,\n response_types_supported: ['code'],\n\n // OAuth 2.1 / MCP requirements\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n token_endpoint_auth_methods_supported: ['none'], // Public client\n\n // Optional but useful\n registration_endpoint: `${baseUrl}/register`,\n scopes_supported: ['productive'],\n service_documentation: 'https://github.com/studiometa/productive-tools',\n };\n});\n\n/**\n * Dynamic Client Registration endpoint (RFC 7591)\n * POST /register\n *\n * MCP servers SHOULD support DCR to allow clients to register automatically.\n * Since we use stateless tokens, we accept any registration and return\n * a generated client_id.\n */\nexport const registerHandler = defineHandler(async (event: H3Event) => {\n event.res.headers.set('Content-Type', 'application/json');\n\n let body: Record<string, unknown>;\n try {\n body = (await event.req.json()) as Record<string, unknown>;\n } catch {\n event.res.status = 400;\n return {\n error: 'invalid_request',\n error_description: 'Invalid JSON body',\n };\n }\n\n // Extract client metadata\n const clientName = (body.client_name as string) || 'MCP Client';\n const redirectUris = (body.redirect_uris as string[]) || [];\n\n // Generate a client_id based on the registration\n // Since we're stateless, we encode minimal info in the client_id\n const clientId = Buffer.from(\n JSON.stringify({\n name: clientName,\n ts: Date.now(),\n }),\n ).toString('base64url');\n\n event.res.status = 201;\n return {\n client_id: clientId,\n client_name: clientName,\n redirect_uris: redirectUris,\n token_endpoint_auth_method: 'none',\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n };\n});\n\n/**\n * Authorization endpoint - shows login form\n * GET /authorize\n */\nexport const authorizeGetHandler = defineHandler((event: H3Event) => {\n const query = getQuery(event);\n\n // Extract OAuth parameters\n const clientId = query.client_id as string;\n const redirectUri = query.redirect_uri as string;\n const state = query.state as string;\n const codeChallenge = query.code_challenge as string;\n const codeChallengeMethod = query.code_challenge_method as string;\n const scope = query.scope as string;\n\n // Validate required parameters per OAuth 2.1\n if (!redirectUri) {\n event.res.headers.set('Content-Type', 'text/html; charset=utf-8');\n event.res.status = 400;\n return renderErrorPage('Missing required parameter: redirect_uri');\n }\n\n // PKCE is REQUIRED for public clients per MCP spec\n if (!codeChallenge) {\n // Redirect back with error per OAuth spec\n const errorUrl = new URL(redirectUri);\n errorUrl.searchParams.set('error', 'invalid_request');\n errorUrl.searchParams.set('error_description', 'code_challenge is required');\n if (state) errorUrl.searchParams.set('state', state);\n return redirect(errorUrl.toString());\n }\n\n if (codeChallengeMethod && codeChallengeMethod !== 'S256') {\n const errorUrl = new URL(redirectUri);\n errorUrl.searchParams.set('error', 'invalid_request');\n errorUrl.searchParams.set('error_description', 'Only S256 code_challenge_method is supported');\n if (state) errorUrl.searchParams.set('state', state);\n return redirect(errorUrl.toString());\n }\n\n event.res.headers.set('Content-Type', 'text/html; charset=utf-8');\n\n // Render login form\n return renderLoginForm({\n clientId,\n redirectUri,\n state,\n codeChallenge,\n codeChallengeMethod: codeChallengeMethod || 'S256',\n scope,\n });\n});\n\n/**\n * Authorization endpoint - process login\n * POST /authorize\n */\nexport const authorizePostHandler = defineHandler(async (event: H3Event) => {\n const formData = await event.req.formData();\n const body = Object.fromEntries(formData.entries()) as Record<string, string>;\n\n const { orgId, apiToken, userId, redirectUri, state, codeChallenge, codeChallengeMethod } = body;\n\n // Validate redirect URI first (security requirement)\n if (!redirectUri) {\n event.res.headers.set('Content-Type', 'text/html; charset=utf-8');\n event.res.status = 400;\n return renderErrorPage('Missing redirect_uri parameter');\n }\n\n // Validate redirect URI format (must be HTTPS or localhost)\n try {\n const uri = new URL(redirectUri);\n const isLocalhost = uri.hostname === 'localhost' || uri.hostname === '127.0.0.1';\n const isHttps = uri.protocol === 'https:';\n if (!isLocalhost && !isHttps) {\n event.res.status = 400;\n return renderErrorPage('redirect_uri must be HTTPS or localhost');\n }\n } catch {\n event.res.status = 400;\n return renderErrorPage('Invalid redirect_uri format');\n }\n\n // Validate required credentials\n if (!orgId || !apiToken) {\n event.res.headers.set('Content-Type', 'text/html; charset=utf-8');\n return renderLoginForm({\n redirectUri,\n state,\n codeChallenge,\n codeChallengeMethod,\n error: 'Organization ID and API Token are required',\n });\n }\n\n // Create encrypted authorization code with PKCE challenge\n const code = createAuthCode({\n orgId,\n apiToken,\n userId: userId || undefined,\n codeChallenge,\n codeChallengeMethod: codeChallengeMethod || 'S256',\n });\n\n // Build redirect URL with authorization code\n const redirectUrl = new URL(redirectUri);\n redirectUrl.searchParams.set('code', code);\n if (state) {\n redirectUrl.searchParams.set('state', state);\n }\n\n // Show success page with auto-redirect\n event.res.headers.set('Content-Type', 'text/html; charset=utf-8');\n return renderSuccessPage(redirectUrl.toString());\n});\n\n/**\n * Token endpoint - exchange code for access token\n * POST /token\n *\n * Supports:\n * - authorization_code grant (with PKCE validation)\n * - refresh_token grant\n */\nexport const tokenHandler = defineHandler(async (event: H3Event) => {\n event.res.headers.set('Content-Type', 'application/json');\n\n let body: Record<string, string>;\n const contentType = event.req.headers.get('content-type') || '';\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n const rawText = await event.req.text();\n body = Object.fromEntries(new URLSearchParams(rawText));\n } else {\n body = (await event.req.json()) as Record<string, string>;\n }\n\n const { grant_type, code, code_verifier, refresh_token } = body;\n\n // Handle refresh token grant\n if (grant_type === 'refresh_token') {\n return handleRefreshToken(event, refresh_token);\n }\n\n // Validate authorization code grant\n if (grant_type !== 'authorization_code') {\n event.res.status = 400;\n return {\n error: 'unsupported_grant_type',\n error_description: 'Supported grant types: authorization_code, refresh_token',\n };\n }\n\n if (!code) {\n event.res.status = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing authorization code',\n };\n }\n\n if (!code_verifier) {\n event.res.status = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing code_verifier (PKCE required)',\n };\n }\n\n try {\n // Decode the authorization code\n const payload = decodeAuthCode(code);\n\n // Validate PKCE: SHA256(code_verifier) must equal code_challenge\n if (payload.codeChallenge) {\n const expectedChallenge = createS256Challenge(code_verifier);\n if (expectedChallenge !== payload.codeChallenge) {\n event.res.status = 400;\n return {\n error: 'invalid_grant',\n error_description: 'Invalid code_verifier',\n };\n }\n }\n\n // Create access token (base64 encoded credentials)\n const accessToken = createAuthToken({\n organizationId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n });\n\n // Create refresh token (encrypted credentials, longer expiry)\n const refreshToken = createAuthCode(\n {\n orgId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n },\n 86400 * 30, // 30 days\n );\n\n return {\n access_token: accessToken,\n token_type: 'Bearer',\n expires_in: 3600, // 1 hour (access tokens should be short-lived)\n refresh_token: refreshToken,\n };\n } catch (error) {\n event.res.status = 400;\n return {\n error: 'invalid_grant',\n error_description: error instanceof Error ? error.message : 'Invalid authorization code',\n };\n }\n});\n\n/**\n * Handle refresh token grant\n */\nfunction handleRefreshToken(event: H3Event, refreshToken: string | undefined) {\n if (!refreshToken) {\n event.res.status = 400;\n return {\n error: 'invalid_request',\n error_description: 'Missing refresh_token',\n };\n }\n\n try {\n // Decode refresh token (it's just an encrypted auth code with longer expiry)\n const payload = decodeAuthCode(refreshToken);\n\n // Create new access token\n const accessToken = createAuthToken({\n organizationId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n });\n\n // Create new refresh token (rotate for security)\n const newRefreshToken = createAuthCode(\n {\n orgId: payload.orgId,\n apiToken: payload.apiToken,\n userId: payload.userId,\n },\n 86400 * 30, // 30 days\n );\n\n return {\n access_token: accessToken,\n token_type: 'Bearer',\n expires_in: 3600,\n refresh_token: newRefreshToken,\n };\n } catch (error) {\n event.res.status = 400;\n return {\n error: 'invalid_grant',\n error_description: error instanceof Error ? error.message : 'Invalid refresh token',\n };\n }\n}\n\n/**\n * Create S256 PKCE challenge from verifier\n * SHA256(code_verifier) encoded as base64url\n */\nfunction createS256Challenge(codeVerifier: string): string {\n return createHash('sha256').update(codeVerifier).digest('base64url');\n}\n\n/**\n * Render the login form HTML\n */\nfunction renderLoginForm(params: {\n clientId?: string;\n redirectUri?: string;\n state?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n scope?: string;\n error?: string;\n}): string {\n const { redirectUri, state, codeChallenge, codeChallengeMethod, error } = params;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Connect to Productive.io</title>\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n padding: 40px;\n width: 100%;\n max-width: 420px;\n }\n .logo {\n text-align: center;\n margin-bottom: 24px;\n }\n .logo svg {\n width: 48px;\n height: 48px;\n }\n h1 {\n text-align: center;\n color: #1a1a2e;\n font-size: 24px;\n margin-bottom: 8px;\n }\n .subtitle {\n text-align: center;\n color: #666;\n font-size: 14px;\n margin-bottom: 32px;\n }\n .error {\n background: #fee2e2;\n border: 1px solid #fecaca;\n color: #dc2626;\n padding: 12px 16px;\n border-radius: 8px;\n margin-bottom: 24px;\n font-size: 14px;\n }\n .form-group {\n margin-bottom: 20px;\n }\n label {\n display: block;\n font-size: 14px;\n font-weight: 500;\n color: #374151;\n margin-bottom: 6px;\n }\n input {\n width: 100%;\n padding: 12px 16px;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n font-size: 16px;\n transition: border-color 0.2s, box-shadow 0.2s;\n }\n input:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);\n }\n input::placeholder {\n color: #9ca3af;\n }\n .help-text {\n font-size: 12px;\n color: #6b7280;\n margin-top: 4px;\n }\n button {\n width: 100%;\n padding: 14px 24px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n button:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n }\n button:active {\n transform: translateY(0);\n }\n .footer {\n text-align: center;\n margin-top: 24px;\n font-size: 12px;\n color: #9ca3af;\n }\n .footer a {\n color: #667eea;\n text-decoration: none;\n }\n .footer a:hover {\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"logo\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 2L2 7L12 12L22 7L12 2Z\" stroke=\"#667eea\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M2 17L12 22L22 17\" stroke=\"#764ba2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <path d=\"M2 12L12 17L22 12\" stroke=\"#667eea\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <h1>Connect to Productive.io</h1>\n <p class=\"subtitle\">Enter your Productive.io credentials to connect with Claude</p>\n \n ${error ? `<div class=\"error\">${escapeHtml(error)}</div>` : ''}\n \n <form method=\"POST\" action=\"/authorize\">\n <input type=\"hidden\" name=\"redirectUri\" value=\"${escapeHtml(redirectUri || '')}\">\n <input type=\"hidden\" name=\"state\" value=\"${escapeHtml(state || '')}\">\n <input type=\"hidden\" name=\"codeChallenge\" value=\"${escapeHtml(codeChallenge || '')}\">\n <input type=\"hidden\" name=\"codeChallengeMethod\" value=\"${escapeHtml(codeChallengeMethod || 'S256')}\">\n \n <div class=\"form-group\">\n <label for=\"orgId\">Organization ID *</label>\n <input type=\"text\" id=\"orgId\" name=\"orgId\" required placeholder=\"e.g., 12345\">\n <p class=\"help-text\">Found in Settings → API integrations</p>\n </div>\n \n <div class=\"form-group\">\n <label for=\"apiToken\">API Token *</label>\n <input type=\"password\" id=\"apiToken\" name=\"apiToken\" required placeholder=\"pk_...\">\n <p class=\"help-text\">Generate at Settings → API integrations → Generate new token</p>\n </div>\n \n <div class=\"form-group\">\n <label for=\"userId\">User ID (optional)</label>\n <input type=\"text\" id=\"userId\" name=\"userId\" placeholder=\"e.g., 67890\">\n <p class=\"help-text\">Required for creating time entries. Found in your profile URL.</p>\n </div>\n \n <button type=\"submit\">Connect to Productive</button>\n </form>\n \n <p class=\"footer\">\n Your credentials are encrypted and sent directly to Claude.<br>\n <a href=\"https://developer.productive.io\" target=\"_blank\">Productive.io API Documentation</a>\n </p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Render success page with auto-redirect\n */\nfunction renderSuccessPage(redirectUrl: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"refresh\" content=\"2;url=${escapeHtml(redirectUrl)}\">\n <title>Connected - Productive MCP</title>\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n padding: 40px;\n width: 100%;\n max-width: 420px;\n text-align: center;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin: 0 auto 24px;\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: white;\n }\n h1 {\n color: #1a1a2e;\n font-size: 24px;\n margin-bottom: 8px;\n }\n .message {\n color: #666;\n font-size: 14px;\n margin-bottom: 24px;\n }\n .spinner {\n width: 24px;\n height: 24px;\n border: 3px solid #e5e7eb;\n border-top-color: #667eea;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n margin: 0 auto 16px;\n }\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n .redirect-text {\n color: #9ca3af;\n font-size: 13px;\n margin-bottom: 16px;\n }\n .manual-link {\n color: #667eea;\n text-decoration: none;\n font-size: 14px;\n }\n .manual-link:hover {\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"success-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 6L9 17L4 12\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n <h1>Successfully Connected!</h1>\n <p class=\"message\">Your Productive.io credentials have been verified.</p>\n <div class=\"spinner\"></div>\n <p class=\"redirect-text\">Redirecting to Claude Desktop...</p>\n <a href=\"${escapeHtml(redirectUrl)}\" class=\"manual-link\">Click here if not redirected automatically</a>\n </div>\n <script>\n // Redirect after a short delay (backup for meta refresh)\n setTimeout(function() {\n window.location.href = ${JSON.stringify(redirectUrl)};\n }, 2000);\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Render error page\n */\nfunction renderErrorPage(message: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Error - Productive MCP</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f3f4f6;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n }\n .container {\n background: white;\n border-radius: 16px;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n padding: 40px;\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #dc2626;\n margin-bottom: 16px;\n }\n p {\n color: #6b7280;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Error</h1>\n <p>${escapeHtml(message)}</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n"],"mappings":"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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -14,68 +14,68 @@ export type { Resource, Action, ReportType } from '@studiometa/productive-core';
|
|
|
14
14
|
* Values derived from RESOURCES constant in constants.ts
|
|
15
15
|
*/
|
|
16
16
|
export declare const ResourceSchema: z.ZodEnum<{
|
|
17
|
+
activities: "activities";
|
|
18
|
+
attachments: "attachments";
|
|
19
|
+
batch: "batch";
|
|
20
|
+
bookings: "bookings";
|
|
17
21
|
comments: "comments";
|
|
18
|
-
|
|
19
|
-
tasks: "tasks";
|
|
22
|
+
companies: "companies";
|
|
20
23
|
custom_fields: "custom_fields";
|
|
21
|
-
services: "services";
|
|
22
24
|
deals: "deals";
|
|
23
|
-
bookings: "bookings";
|
|
24
|
-
timers: "timers";
|
|
25
|
-
projects: "projects";
|
|
26
|
-
people: "people";
|
|
27
|
-
companies: "companies";
|
|
28
|
-
attachments: "attachments";
|
|
29
25
|
discussions: "discussions";
|
|
30
26
|
pages: "pages";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
workflows: "workflows";
|
|
27
|
+
people: "people";
|
|
28
|
+
projects: "projects";
|
|
34
29
|
reports: "reports";
|
|
35
30
|
search: "search";
|
|
31
|
+
services: "services";
|
|
36
32
|
summaries: "summaries";
|
|
33
|
+
tasks: "tasks";
|
|
34
|
+
time: "time";
|
|
35
|
+
timers: "timers";
|
|
36
|
+
workflows: "workflows";
|
|
37
37
|
}>;
|
|
38
38
|
/**
|
|
39
39
|
* Actions available for resources
|
|
40
40
|
* Values derived from ACTIONS constant in constants.ts
|
|
41
41
|
*/
|
|
42
42
|
export declare const ActionSchema: z.ZodEnum<{
|
|
43
|
-
|
|
43
|
+
complete_task: "complete_task";
|
|
44
|
+
context: "context";
|
|
44
45
|
create: "create";
|
|
45
|
-
start: "start";
|
|
46
|
-
update: "update";
|
|
47
|
-
get: "get";
|
|
48
46
|
delete: "delete";
|
|
49
|
-
|
|
50
|
-
stop: "stop";
|
|
51
|
-
context: "context";
|
|
52
|
-
reopen: "reopen";
|
|
53
|
-
run: "run";
|
|
54
|
-
me: "me";
|
|
55
|
-
complete_task: "complete_task";
|
|
56
|
-
log_day: "log_day";
|
|
57
|
-
weekly_standup: "weekly_standup";
|
|
47
|
+
get: "get";
|
|
58
48
|
help: "help";
|
|
59
|
-
|
|
49
|
+
list: "list";
|
|
50
|
+
log_day: "log_day";
|
|
51
|
+
me: "me";
|
|
60
52
|
my_day: "my_day";
|
|
61
53
|
project_health: "project_health";
|
|
54
|
+
reopen: "reopen";
|
|
55
|
+
resolve: "resolve";
|
|
56
|
+
run: "run";
|
|
57
|
+
schema: "schema";
|
|
58
|
+
start: "start";
|
|
59
|
+
stop: "stop";
|
|
62
60
|
team_pulse: "team_pulse";
|
|
61
|
+
update: "update";
|
|
62
|
+
weekly_standup: "weekly_standup";
|
|
63
63
|
}>;
|
|
64
64
|
/**
|
|
65
65
|
* Report types available in Productive.io
|
|
66
66
|
* Values derived from REPORT_TYPES constant in constants.ts
|
|
67
67
|
*/
|
|
68
68
|
export declare const ReportTypeSchema: z.ZodEnum<{
|
|
69
|
-
time_reports: "time_reports";
|
|
70
69
|
budget_reports: "budget_reports";
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
company_reports: "company_reports";
|
|
71
|
+
deal_reports: "deal_reports";
|
|
73
72
|
invoice_reports: "invoice_reports";
|
|
74
73
|
payment_reports: "payment_reports";
|
|
74
|
+
person_reports: "person_reports";
|
|
75
|
+
project_reports: "project_reports";
|
|
75
76
|
service_reports: "service_reports";
|
|
76
77
|
task_reports: "task_reports";
|
|
77
|
-
|
|
78
|
-
deal_reports: "deal_reports";
|
|
78
|
+
time_reports: "time_reports";
|
|
79
79
|
timesheet_reports: "timesheet_reports";
|
|
80
80
|
}>;
|
|
81
81
|
/**
|
|
@@ -164,48 +164,48 @@ export declare const FilterSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodU
|
|
|
164
164
|
*/
|
|
165
165
|
export declare const ProductiveToolInputSchema: z.ZodObject<{
|
|
166
166
|
resource: z.ZodEnum<{
|
|
167
|
+
activities: "activities";
|
|
168
|
+
attachments: "attachments";
|
|
169
|
+
batch: "batch";
|
|
170
|
+
bookings: "bookings";
|
|
167
171
|
comments: "comments";
|
|
168
|
-
|
|
169
|
-
tasks: "tasks";
|
|
172
|
+
companies: "companies";
|
|
170
173
|
custom_fields: "custom_fields";
|
|
171
|
-
services: "services";
|
|
172
174
|
deals: "deals";
|
|
173
|
-
bookings: "bookings";
|
|
174
|
-
timers: "timers";
|
|
175
|
-
projects: "projects";
|
|
176
|
-
people: "people";
|
|
177
|
-
companies: "companies";
|
|
178
|
-
attachments: "attachments";
|
|
179
175
|
discussions: "discussions";
|
|
180
176
|
pages: "pages";
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
workflows: "workflows";
|
|
177
|
+
people: "people";
|
|
178
|
+
projects: "projects";
|
|
184
179
|
reports: "reports";
|
|
185
180
|
search: "search";
|
|
181
|
+
services: "services";
|
|
186
182
|
summaries: "summaries";
|
|
183
|
+
tasks: "tasks";
|
|
184
|
+
time: "time";
|
|
185
|
+
timers: "timers";
|
|
186
|
+
workflows: "workflows";
|
|
187
187
|
}>;
|
|
188
188
|
action: z.ZodEnum<{
|
|
189
|
-
|
|
189
|
+
complete_task: "complete_task";
|
|
190
|
+
context: "context";
|
|
190
191
|
create: "create";
|
|
191
|
-
start: "start";
|
|
192
|
-
update: "update";
|
|
193
|
-
get: "get";
|
|
194
192
|
delete: "delete";
|
|
195
|
-
|
|
196
|
-
stop: "stop";
|
|
197
|
-
context: "context";
|
|
198
|
-
reopen: "reopen";
|
|
199
|
-
run: "run";
|
|
200
|
-
me: "me";
|
|
201
|
-
complete_task: "complete_task";
|
|
202
|
-
log_day: "log_day";
|
|
203
|
-
weekly_standup: "weekly_standup";
|
|
193
|
+
get: "get";
|
|
204
194
|
help: "help";
|
|
205
|
-
|
|
195
|
+
list: "list";
|
|
196
|
+
log_day: "log_day";
|
|
197
|
+
me: "me";
|
|
206
198
|
my_day: "my_day";
|
|
207
199
|
project_health: "project_health";
|
|
200
|
+
reopen: "reopen";
|
|
201
|
+
resolve: "resolve";
|
|
202
|
+
run: "run";
|
|
203
|
+
schema: "schema";
|
|
204
|
+
start: "start";
|
|
205
|
+
stop: "stop";
|
|
208
206
|
team_pulse: "team_pulse";
|
|
207
|
+
update: "update";
|
|
208
|
+
weekly_standup: "weekly_standup";
|
|
209
209
|
}>;
|
|
210
210
|
id: z.ZodOptional<z.ZodString>;
|
|
211
211
|
filter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -239,16 +239,16 @@ export declare const ProductiveToolInputSchema: z.ZodObject<{
|
|
|
239
239
|
ended_on: z.ZodOptional<z.ZodString>;
|
|
240
240
|
event_id: z.ZodOptional<z.ZodString>;
|
|
241
241
|
report_type: z.ZodOptional<z.ZodEnum<{
|
|
242
|
-
time_reports: "time_reports";
|
|
243
242
|
budget_reports: "budget_reports";
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
company_reports: "company_reports";
|
|
244
|
+
deal_reports: "deal_reports";
|
|
246
245
|
invoice_reports: "invoice_reports";
|
|
247
246
|
payment_reports: "payment_reports";
|
|
247
|
+
person_reports: "person_reports";
|
|
248
|
+
project_reports: "project_reports";
|
|
248
249
|
service_reports: "service_reports";
|
|
249
250
|
task_reports: "task_reports";
|
|
250
|
-
|
|
251
|
-
deal_reports: "deal_reports";
|
|
251
|
+
time_reports: "time_reports";
|
|
252
252
|
timesheet_reports: "timesheet_reports";
|
|
253
253
|
}>>;
|
|
254
254
|
group: z.ZodOptional<z.ZodString>;
|
package/dist/server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as VERSION } from "./version-
|
|
2
|
+
import { t as VERSION } from "./version-BFw4junA.js";
|
|
3
3
|
import "./handlers-t95fhdps.js";
|
|
4
4
|
import { createHttpApp } from "./http.js";
|
|
5
|
-
import {
|
|
5
|
+
import { toNodeHandler } from "h3";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
/**
|
|
8
8
|
* Productive MCP Server - HTTP Transport
|
|
@@ -31,7 +31,7 @@ var DEFAULT_HOST = "0.0.0.0";
|
|
|
31
31
|
*/
|
|
32
32
|
function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
|
|
33
33
|
return new Promise((resolve) => {
|
|
34
|
-
const server = createServer(
|
|
34
|
+
const server = createServer(toNodeHandler(createHttpApp()));
|
|
35
35
|
server.listen(port, host, () => {
|
|
36
36
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
37
37
|
console.log(`Productive MCP server v${VERSION}`);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Productive MCP Server - HTTP Transport\n *\n * This is the remote HTTP server mode for Claude Desktop custom connectors.\n * Credentials are passed via Bearer token in the Authorization header.\n *\n * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)\n *\n * Generate your token:\n * echo -n \"YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID\" | base64\n *\n * Usage:\n * productive-mcp-server\n * PORT=3000 productive-mcp-server\n *\n * Claude Desktop custom connector config:\n * Name: Productive\n * URL: https://productive.mcp.ikko.dev\n * (No OAuth needed - uses Bearer token)\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Productive MCP Server - HTTP Transport\n *\n * This is the remote HTTP server mode for Claude Desktop custom connectors.\n * Credentials are passed via Bearer token in the Authorization header.\n *\n * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)\n *\n * Generate your token:\n * echo -n \"YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID\" | base64\n *\n * Usage:\n * productive-mcp-server\n * PORT=3000 productive-mcp-server\n *\n * Claude Desktop custom connector config:\n * Name: Productive\n * URL: https://productive.mcp.ikko.dev\n * (No OAuth needed - uses Bearer token)\n */\n\nimport { toNodeHandler } from 'h3';\nimport { createServer, type Server } from 'node:http';\n\nimport { createHttpApp } from './http.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_HOST = '0.0.0.0';\n\n/**\n * Start the HTTP server\n */\nexport function startHttpServer(\n port: number = DEFAULT_PORT,\n host: string = DEFAULT_HOST,\n): Promise<Server> {\n return new Promise((resolve) => {\n const app = createHttpApp();\n const server = createServer(toNodeHandler(app));\n\n server.listen(port, host, () => {\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n console.log(`Productive MCP server v${VERSION}`);\n console.log(`Node.js ${process.version}`);\n console.log('');\n console.log(`Running at http://${displayHost}:${port}`);\n console.log('');\n console.log('Endpoints:');\n console.log(` POST http://${displayHost}:${port}/mcp - MCP JSON-RPC endpoint`);\n console.log(` GET http://${displayHost}:${port}/health - Health check`);\n console.log('');\n console.log('OAuth 2.0 (MCP auth spec compliant):');\n console.log(` GET http://${displayHost}:${port}/.well-known/oauth-authorization-server`);\n console.log(` POST http://${displayHost}:${port}/register - Dynamic Client Registration`);\n console.log(` GET http://${displayHost}:${port}/authorize - Authorization endpoint`);\n console.log(` POST http://${displayHost}:${port}/token - Token endpoint`);\n console.log('');\n console.log('Authentication:');\n console.log(' Option 1: OAuth flow (Claude Desktop will handle this automatically)');\n console.log(' Option 2: Bearer token in Authorization header');\n console.log(' Token format: base64(organizationId:apiToken:userId)');\n console.log('');\n if (!process.env.OAUTH_SECRET) {\n console.log('⚠️ WARNING: OAUTH_SECRET not set. Set it in production!');\n console.log(' export OAUTH_SECRET=\"your-random-secret-here\"');\n console.log('');\n }\n resolve(server);\n });\n });\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('/productive-mcp-server') ||\n process.argv[1]?.endsWith('\\\\productive-mcp-server');\n\nif (isMainModule) {\n const port = Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10);\n const host = process.env.HOST || DEFAULT_HOST;\n\n startHttpServer(port, host).catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAM,eAAe;AACrB,IAAM,eAAe;;;;AAKrB,SAAgB,gBACd,OAAe,cACf,OAAe,cACE;AACjB,QAAO,IAAI,SAAS,YAAY;EAE9B,MAAM,SAAS,aAAa,cADhB,eAAe,CACmB,CAAC;AAE/C,SAAO,OAAO,MAAM,YAAY;GAC9B,MAAM,cAAc,SAAS,YAAY,cAAc;AACvD,WAAQ,IAAI,0BAA0B,UAAU;AAChD,WAAQ,IAAI,WAAW,QAAQ,UAAU;AACzC,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,qBAAqB,YAAY,GAAG,OAAO;AACvD,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,aAAa;AACzB,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,8BAA8B;AAC/E,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,wBAAwB;AACzE,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,uCAAuC;AACnD,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,yCAAyC;AAC1F,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,yCAAyC;AAC1F,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,qCAAqC;AACtF,WAAQ,IAAI,iBAAiB,YAAY,GAAG,KAAK,yBAAyB;AAC1E,WAAQ,IAAI,GAAG;AACf,WAAQ,IAAI,kBAAkB;AAC9B,WAAQ,IAAI,yEAAyE;AACrF,WAAQ,IAAI,mDAAmD;AAC/D,WAAQ,IAAI,mEAAmE;AAC/E,WAAQ,IAAI,GAAG;AACf,OAAI,CAAC,QAAQ,IAAI,cAAc;AAC7B,YAAQ,IAAI,2DAA2D;AACvE,YAAQ,IAAI,qDAAmD;AAC/D,YAAQ,IAAI,GAAG;;AAEjB,WAAQ,OAAO;IACf;GACF;;AASJ,IAJE,OAAO,KAAK,QAAQ,UAAU,QAAQ,KAAK,QAC3C,QAAQ,KAAK,IAAI,SAAS,yBAAyB,IACnD,QAAQ,KAAK,IAAI,SAAS,0BAA0B,CAMpD,iBAHa,OAAO,SAAS,QAAQ,IAAI,QAAQ,OAAO,aAAa,EAAE,GAAG,EAC7D,QAAQ,IAAI,QAAQ,aAEN,CAAC,OAAO,UAAU;AAC3C,SAAQ,MAAM,gBAAgB,MAAM;AACpC,SAAQ,KAAK,EAAE;EACf"}
|
package/dist/stdio.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type ToolResult = CallToolResult;
|
|
|
10
10
|
* Get all available tools (including stdio-only configuration tools)
|
|
11
11
|
*/
|
|
12
12
|
export declare function getAvailableTools(): {
|
|
13
|
+
description?: string | undefined;
|
|
13
14
|
inputSchema: {
|
|
14
15
|
[x: string]: unknown;
|
|
15
16
|
type: "object";
|
|
@@ -18,8 +19,6 @@ export declare function getAvailableTools(): {
|
|
|
18
19
|
} | undefined;
|
|
19
20
|
required?: string[] | undefined;
|
|
20
21
|
};
|
|
21
|
-
name: string;
|
|
22
|
-
description?: string | undefined;
|
|
23
22
|
outputSchema?: {
|
|
24
23
|
[x: string]: unknown;
|
|
25
24
|
type: "object";
|
|
@@ -36,7 +35,7 @@ export declare function getAvailableTools(): {
|
|
|
36
35
|
openWorldHint?: boolean | undefined;
|
|
37
36
|
} | undefined;
|
|
38
37
|
execution?: {
|
|
39
|
-
taskSupport?: "
|
|
38
|
+
taskSupport?: "forbidden" | "optional" | "required" | undefined;
|
|
40
39
|
} | undefined;
|
|
41
40
|
_meta?: {
|
|
42
41
|
[x: string]: unknown;
|
|
@@ -45,8 +44,9 @@ export declare function getAvailableTools(): {
|
|
|
45
44
|
src: string;
|
|
46
45
|
mimeType?: string | undefined;
|
|
47
46
|
sizes?: string[] | undefined;
|
|
48
|
-
theme?: "
|
|
47
|
+
theme?: "dark" | "light" | undefined;
|
|
49
48
|
}[] | undefined;
|
|
49
|
+
name: string;
|
|
50
50
|
title?: string | undefined;
|
|
51
51
|
}[];
|
|
52
52
|
/**
|
|
@@ -230,7 +230,7 @@ async function readResource(uri, credentials) {
|
|
|
230
230
|
}
|
|
231
231
|
throw new Error(`Unknown resource URI: ${uri}. Available static resources: ${STATIC_RESOURCES.map((r) => r.uri).join(", ")}. Available dynamic resources: ${DYNAMIC_RESOURCES.map((r) => r.uri).join(", ")}. Resource templates: ${RESOURCE_TEMPLATES.map((t) => t.uriTemplate).join(", ")}.`);
|
|
232
232
|
}
|
|
233
|
-
const VERSION = "0.10.
|
|
233
|
+
const VERSION = "0.10.9";
|
|
234
234
|
export { INSTRUCTIONS as a, readResource as i, listResourceTemplates as n, listResources as r, VERSION as t };
|
|
235
235
|
|
|
236
|
-
//# sourceMappingURL=version-
|
|
236
|
+
//# sourceMappingURL=version-BFw4junA.js.map
|