@studiometa/productive-mcp 0.10.8 → 0.10.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/auth.js +2 -0
- package/dist/auth.js.map +1 -1
- package/dist/crypto.js +2 -0
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/handlers/activities.d.ts +99 -3
- package/dist/handlers/activities.d.ts.map +1 -1
- package/dist/handlers/attachments.d.ts +99 -3
- package/dist/handlers/attachments.d.ts.map +1 -1
- package/dist/handlers/bookings.d.ts +99 -3
- package/dist/handlers/bookings.d.ts.map +1 -1
- package/dist/handlers/comments.d.ts +99 -3
- package/dist/handlers/comments.d.ts.map +1 -1
- package/dist/handlers/companies.d.ts +99 -3
- package/dist/handlers/companies.d.ts.map +1 -1
- package/dist/handlers/custom-fields.d.ts +99 -3
- package/dist/handlers/custom-fields.d.ts.map +1 -1
- package/dist/handlers/deals.d.ts +99 -3
- package/dist/handlers/deals.d.ts.map +1 -1
- package/dist/handlers/discussions.d.ts +99 -3
- package/dist/handlers/discussions.d.ts.map +1 -1
- package/dist/handlers/help.d.ts.map +1 -1
- package/dist/handlers/pages.d.ts +99 -3
- package/dist/handlers/pages.d.ts.map +1 -1
- package/dist/handlers/projects.d.ts +99 -3
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/schema.d.ts.map +1 -1
- package/dist/handlers/services.d.ts +99 -3
- package/dist/handlers/services.d.ts.map +1 -1
- package/dist/handlers/tasks.d.ts +99 -3
- package/dist/handlers/tasks.d.ts.map +1 -1
- package/dist/handlers/time.d.ts +99 -3
- package/dist/handlers/time.d.ts.map +1 -1
- package/dist/handlers/timers.d.ts +99 -3
- package/dist/handlers/timers.d.ts.map +1 -1
- package/dist/handlers/types.d.ts +1 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/{handlers-t95fhdps.js → handlers-vtRpc-Lx.js} +101 -25
- package/dist/handlers-vtRpc-Lx.js.map +1 -0
- package/dist/handlers.js +1 -1
- package/dist/http-CVE4qtko.js +6541 -0
- package/dist/http-CVE4qtko.js.map +1 -0
- package/dist/http.d.ts +12 -7
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +2 -159
- package/dist/index.js +4 -3
- package/dist/oauth.d.ts +9 -9
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +41 -149
- package/dist/oauth.js.map +1 -1
- package/dist/schema.d.ts +62 -62
- package/dist/server.js +6 -5
- package/dist/server.js.map +1 -1
- package/dist/{stdio-Bi1Lvp8O.js → stdio-BFK9AcdQ.js} +9 -3
- package/dist/{stdio-Bi1Lvp8O.js.map → stdio-BFK9AcdQ.js.map} +1 -1
- package/dist/stdio.d.ts +4 -4
- package/dist/stdio.js +1 -2
- package/dist/tools.js +4 -2
- package/dist/tools.js.map +1 -1
- package/dist/{version-DpBFJ7eV.js → version-Cy8UEAT1.js} +13 -7
- package/dist/{version-DpBFJ7eV.js.map → version-Cy8UEAT1.js.map} +1 -1
- package/package.json +10 -18
- package/dist/handlers-t95fhdps.js.map +0 -1
- package/dist/http.js.map +0 -1
package/dist/http.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* This module contains the app/router creation logic for the HTTP transport.
|
|
5
5
|
* The actual server startup is in server.ts.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
9
|
+
import { H3 } from 'h3';
|
|
8
10
|
/**
|
|
9
11
|
* JSON-RPC error response
|
|
10
12
|
*/
|
|
@@ -35,6 +37,7 @@ export declare function handleInitialize(): {
|
|
|
35
37
|
};
|
|
36
38
|
capabilities: {
|
|
37
39
|
tools: {};
|
|
40
|
+
prompts: {};
|
|
38
41
|
resources: {};
|
|
39
42
|
};
|
|
40
43
|
instructions: string;
|
|
@@ -44,6 +47,7 @@ export declare function handleInitialize(): {
|
|
|
44
47
|
*/
|
|
45
48
|
export declare function handleToolsList(): {
|
|
46
49
|
tools: {
|
|
50
|
+
description?: string | undefined;
|
|
47
51
|
inputSchema: {
|
|
48
52
|
[x: string]: unknown;
|
|
49
53
|
type: "object";
|
|
@@ -52,8 +56,6 @@ export declare function handleToolsList(): {
|
|
|
52
56
|
} | undefined;
|
|
53
57
|
required?: string[] | undefined;
|
|
54
58
|
};
|
|
55
|
-
name: string;
|
|
56
|
-
description?: string | undefined;
|
|
57
59
|
outputSchema?: {
|
|
58
60
|
[x: string]: unknown;
|
|
59
61
|
type: "object";
|
|
@@ -70,7 +72,7 @@ export declare function handleToolsList(): {
|
|
|
70
72
|
openWorldHint?: boolean | undefined;
|
|
71
73
|
} | undefined;
|
|
72
74
|
execution?: {
|
|
73
|
-
taskSupport?: "
|
|
75
|
+
taskSupport?: "forbidden" | "optional" | "required" | undefined;
|
|
74
76
|
} | undefined;
|
|
75
77
|
_meta?: {
|
|
76
78
|
[x: string]: unknown;
|
|
@@ -79,13 +81,16 @@ export declare function handleToolsList(): {
|
|
|
79
81
|
src: string;
|
|
80
82
|
mimeType?: string | undefined;
|
|
81
83
|
sizes?: string[] | undefined;
|
|
82
|
-
theme?: "
|
|
84
|
+
theme?: "dark" | "light" | undefined;
|
|
83
85
|
}[] | undefined;
|
|
86
|
+
name: string;
|
|
84
87
|
title?: string | undefined;
|
|
85
88
|
}[];
|
|
86
89
|
};
|
|
90
|
+
export declare function createHttpMcpServer(): Server;
|
|
91
|
+
export declare function createHttpMcpTransport(): Promise<StreamableHTTPServerTransport>;
|
|
87
92
|
/**
|
|
88
|
-
* Create the
|
|
93
|
+
* Create the H3 application with all routes
|
|
89
94
|
*/
|
|
90
|
-
export declare function createHttpApp():
|
|
95
|
+
export declare function createHttpApp(): H3;
|
|
91
96
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAYnG,OAAO,EAAE,EAAE,EAA+B,MAAM,IAAI,CAAC;AAmBrD;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW;;;;;;;EAM5F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,GAAE,MAAM,GAAG,MAAM,GAAG,IAAW;;;;EAMhF;AAED;;GAEG;AACH,wBAAgB,gBAAgB;;;;;;;;;;;;EAc/B;AAED;;GAEG;AACH,wBAAgB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE9B;AA6DD,wBAAgB,mBAAmB,IAAI,MAAM,CAwD5C;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,6BAA6B,CAAC,CAUrF;AAuBD;;GAEG;AACH,wBAAgB,aAAa,IAAI,EAAE,CA2FlC"}
|
package/dist/http.js
CHANGED
|
@@ -1,159 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
3
|
-
import { TOOLS } from "./tools.js";
|
|
4
|
-
import { parseAuthHeader } from "./auth.js";
|
|
5
|
-
import { authorizeGetHandler, authorizePostHandler, oauthMetadataHandler, registerHandler, tokenHandler } from "./oauth.js";
|
|
6
|
-
import { createApp, createRouter, defineEventHandler, getHeader, readBody, setResponseHeader } from "h3";
|
|
7
|
-
/**
|
|
8
|
-
* HTTP transport handlers for Productive MCP Server
|
|
9
|
-
*
|
|
10
|
-
* This module contains the app/router creation logic for the HTTP transport.
|
|
11
|
-
* The actual server startup is in server.ts.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* JSON-RPC error response
|
|
15
|
-
*/
|
|
16
|
-
function jsonRpcError(code, message, id = null) {
|
|
17
|
-
return {
|
|
18
|
-
jsonrpc: "2.0",
|
|
19
|
-
error: {
|
|
20
|
-
code,
|
|
21
|
-
message
|
|
22
|
-
},
|
|
23
|
-
id
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* JSON-RPC success response
|
|
28
|
-
*/
|
|
29
|
-
function jsonRpcSuccess(result, id = null) {
|
|
30
|
-
return {
|
|
31
|
-
jsonrpc: "2.0",
|
|
32
|
-
result,
|
|
33
|
-
id
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Handle the initialize JSON-RPC method
|
|
38
|
-
*/
|
|
39
|
-
function handleInitialize() {
|
|
40
|
-
return {
|
|
41
|
-
protocolVersion: "2024-11-05",
|
|
42
|
-
serverInfo: {
|
|
43
|
-
name: "productive-mcp",
|
|
44
|
-
version: VERSION
|
|
45
|
-
},
|
|
46
|
-
capabilities: {
|
|
47
|
-
tools: {},
|
|
48
|
-
resources: {}
|
|
49
|
-
},
|
|
50
|
-
instructions: INSTRUCTIONS
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Handle the tools/list JSON-RPC method
|
|
55
|
-
*/
|
|
56
|
-
function handleToolsList() {
|
|
57
|
-
return { tools: TOOLS };
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create the h3 application with all routes
|
|
61
|
-
*/
|
|
62
|
-
function createHttpApp() {
|
|
63
|
-
const app = createApp();
|
|
64
|
-
const router = createRouter();
|
|
65
|
-
router.get("/.well-known/oauth-authorization-server", oauthMetadataHandler);
|
|
66
|
-
router.post("/register", registerHandler);
|
|
67
|
-
router.get("/authorize", authorizeGetHandler);
|
|
68
|
-
router.post("/authorize", authorizePostHandler);
|
|
69
|
-
router.post("/token", tokenHandler);
|
|
70
|
-
router.get("/.well-known/oauth-protected-resource", defineEventHandler((event) => {
|
|
71
|
-
const host = event.node.req.headers.host || "localhost:3000";
|
|
72
|
-
const baseUrl = `${event.node.req.headers["x-forwarded-proto"] || "http"}://${host}`;
|
|
73
|
-
setResponseHeader(event, "Content-Type", "application/json");
|
|
74
|
-
setResponseHeader(event, "Cache-Control", "public, max-age=3600");
|
|
75
|
-
return {
|
|
76
|
-
resource: `${baseUrl}/mcp`,
|
|
77
|
-
authorization_servers: [baseUrl],
|
|
78
|
-
scopes_supported: ["productive"],
|
|
79
|
-
bearer_methods_supported: ["header"]
|
|
80
|
-
};
|
|
81
|
-
}));
|
|
82
|
-
router.get("/", defineEventHandler(() => {
|
|
83
|
-
return {
|
|
84
|
-
status: "ok",
|
|
85
|
-
service: "productive-mcp",
|
|
86
|
-
version: VERSION
|
|
87
|
-
};
|
|
88
|
-
}));
|
|
89
|
-
router.get("/health", defineEventHandler(() => {
|
|
90
|
-
return { status: "ok" };
|
|
91
|
-
}));
|
|
92
|
-
router.post("/mcp", defineEventHandler(async (event) => {
|
|
93
|
-
const credentials = parseAuthHeader(getHeader(event, "authorization"));
|
|
94
|
-
if (!credentials) {
|
|
95
|
-
const host = event.node.req.headers.host || "localhost:3000";
|
|
96
|
-
const baseUrl = `${event.node.req.headers["x-forwarded-proto"] || "http"}://${host}`;
|
|
97
|
-
setResponseHeader(event, "Content-Type", "application/json");
|
|
98
|
-
setResponseHeader(event, "WWW-Authenticate", `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`);
|
|
99
|
-
event.node.res.statusCode = 401;
|
|
100
|
-
return jsonRpcError(-32001, "Authentication required. Provide Bearer token with base64(organizationId:apiToken:userId)");
|
|
101
|
-
}
|
|
102
|
-
setResponseHeader(event, "Content-Type", "application/json");
|
|
103
|
-
let body;
|
|
104
|
-
try {
|
|
105
|
-
body = await readBody(event);
|
|
106
|
-
} catch {
|
|
107
|
-
event.node.res.statusCode = 400;
|
|
108
|
-
return jsonRpcError(-32700, "Parse error: Invalid JSON");
|
|
109
|
-
}
|
|
110
|
-
if (!body || typeof body !== "object") {
|
|
111
|
-
event.node.res.statusCode = 400;
|
|
112
|
-
return jsonRpcError(-32700, "Parse error: Invalid JSON");
|
|
113
|
-
}
|
|
114
|
-
const { method, params, id } = body;
|
|
115
|
-
try {
|
|
116
|
-
if (method === "initialize") return jsonRpcSuccess(handleInitialize(), id ?? null);
|
|
117
|
-
if (method === "tools/list") return jsonRpcSuccess(handleToolsList(), id ?? null);
|
|
118
|
-
if (method === "tools/call") {
|
|
119
|
-
const { name, arguments: args } = params;
|
|
120
|
-
return jsonRpcSuccess(await executeToolWithCredentials(name, args || {}, credentials), id ?? null);
|
|
121
|
-
}
|
|
122
|
-
if (method === "resources/list") return jsonRpcSuccess({ resources: listResources() }, id ?? null);
|
|
123
|
-
if (method === "resources/templates/list") return jsonRpcSuccess({ resourceTemplates: listResourceTemplates() }, id ?? null);
|
|
124
|
-
if (method === "resources/read") {
|
|
125
|
-
const { uri } = params ?? {};
|
|
126
|
-
if (!uri) return jsonRpcError(-32602, "Invalid params: uri is required", id ?? null);
|
|
127
|
-
return jsonRpcSuccess(await readResource(uri, credentials), id ?? null);
|
|
128
|
-
}
|
|
129
|
-
return jsonRpcError(-32601, `Method not found: ${method}`, id ?? null);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return jsonRpcError(-32603, `Internal error: ${error instanceof Error ? error.message : String(error)}`, id ?? null);
|
|
132
|
-
}
|
|
133
|
-
}));
|
|
134
|
-
router.get("/mcp/sse", defineEventHandler(async (event) => {
|
|
135
|
-
if (!parseAuthHeader(getHeader(event, "authorization"))) {
|
|
136
|
-
const host = event.node.req.headers.host || "localhost:3000";
|
|
137
|
-
setResponseHeader(event, "WWW-Authenticate", `Bearer resource_metadata="${`${event.node.req.headers["x-forwarded-proto"] || "http"}://${host}`}/.well-known/oauth-protected-resource"`);
|
|
138
|
-
event.node.res.statusCode = 401;
|
|
139
|
-
return { error: "Authentication required" };
|
|
140
|
-
}
|
|
141
|
-
setResponseHeader(event, "Content-Type", "text/event-stream");
|
|
142
|
-
setResponseHeader(event, "Cache-Control", "no-cache");
|
|
143
|
-
setResponseHeader(event, "Connection", "keep-alive");
|
|
144
|
-
const sessionId = crypto.randomUUID();
|
|
145
|
-
event.node.res.write(`event: session\ndata: ${JSON.stringify({ sessionId })}\n\n`);
|
|
146
|
-
const keepAlive = setInterval(() => {
|
|
147
|
-
event.node.res.write(": keepalive\n\n");
|
|
148
|
-
}, 3e4);
|
|
149
|
-
event.node.req.on("close", () => {
|
|
150
|
-
clearInterval(keepAlive);
|
|
151
|
-
});
|
|
152
|
-
return new Promise(() => {});
|
|
153
|
-
}));
|
|
154
|
-
app.use(router);
|
|
155
|
-
return app;
|
|
156
|
-
}
|
|
157
|
-
export { createHttpApp, handleInitialize, handleToolsList, jsonRpcError, jsonRpcSuccess };
|
|
158
|
-
|
|
159
|
-
//# sourceMappingURL=http.js.map
|
|
1
|
+
import { a as handleToolsList, i as handleInitialize, n as createHttpMcpServer, o as jsonRpcError, r as createHttpMcpTransport, s as jsonRpcSuccess, t as createHttpApp } from "./http-CVE4qtko.js";
|
|
2
|
+
export { createHttpApp, createHttpMcpServer, createHttpMcpTransport, handleInitialize, handleToolsList, jsonRpcError, jsonRpcSuccess };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as INSTRUCTIONS, i as readResource, n as listResourceTemplates, r as listResources, t as VERSION } from "./version-
|
|
3
|
-
import "./
|
|
4
|
-
import { a as handlePrompt, n as getAvailableTools, s as handleToolCall, t as getAvailablePrompts } from "./stdio-Bi1Lvp8O.js";
|
|
2
|
+
import { a as INSTRUCTIONS, i as readResource, n as listResourceTemplates, r as listResources, t as VERSION } from "./version-Cy8UEAT1.js";
|
|
3
|
+
import { a as handlePrompt, n as getAvailableTools, s as handleToolCall, t as getAvailablePrompts } from "./stdio-BFK9AcdQ.js";
|
|
5
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
6
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
7
|
import { getConfig } from "@studiometa/productive-api";
|
|
8
|
+
//#region src/index.ts
|
|
9
9
|
/**
|
|
10
10
|
* Productive MCP Server - Stdio Transport
|
|
11
11
|
*
|
|
@@ -94,6 +94,7 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
94
94
|
console.error("Fatal error:", error);
|
|
95
95
|
process.exit(1);
|
|
96
96
|
});
|
|
97
|
+
//#endregion
|
|
97
98
|
export { createStdioServer, startStdioServer };
|
|
98
99
|
|
|
99
100
|
//# sourceMappingURL=index.js.map
|
package/dist/oauth.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* MCP clients MUST check this endpoint first for server capabilities.
|
|
22
22
|
*/
|
|
23
|
-
export declare const oauthMetadataHandler: import("h3").
|
|
23
|
+
export declare const oauthMetadataHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, {
|
|
24
24
|
issuer: string;
|
|
25
25
|
authorization_endpoint: string;
|
|
26
26
|
token_endpoint: string;
|
|
@@ -40,7 +40,7 @@ export declare const oauthMetadataHandler: import("h3").EventHandler<import("h3"
|
|
|
40
40
|
* Since we use stateless tokens, we accept any registration and return
|
|
41
41
|
* a generated client_id.
|
|
42
42
|
*/
|
|
43
|
-
export declare const registerHandler: import("h3").
|
|
43
|
+
export declare const registerHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
44
44
|
error: string;
|
|
45
45
|
error_description: string;
|
|
46
46
|
client_id?: undefined;
|
|
@@ -50,25 +50,25 @@ export declare const registerHandler: import("h3").EventHandler<import("h3").Eve
|
|
|
50
50
|
grant_types?: undefined;
|
|
51
51
|
response_types?: undefined;
|
|
52
52
|
} | {
|
|
53
|
+
error?: undefined;
|
|
54
|
+
error_description?: undefined;
|
|
53
55
|
client_id: string;
|
|
54
56
|
client_name: string;
|
|
55
57
|
redirect_uris: string[];
|
|
56
58
|
token_endpoint_auth_method: string;
|
|
57
59
|
grant_types: string[];
|
|
58
60
|
response_types: string[];
|
|
59
|
-
error?: undefined;
|
|
60
|
-
error_description?: undefined;
|
|
61
61
|
}>>;
|
|
62
62
|
/**
|
|
63
63
|
* Authorization endpoint - shows login form
|
|
64
64
|
* GET /authorize
|
|
65
65
|
*/
|
|
66
|
-
export declare const authorizeGetHandler: import("h3").
|
|
66
|
+
export declare const authorizeGetHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, string | import("h3").HTTPResponse>;
|
|
67
67
|
/**
|
|
68
68
|
* Authorization endpoint - process login
|
|
69
69
|
* POST /authorize
|
|
70
70
|
*/
|
|
71
|
-
export declare const authorizePostHandler: import("h3").
|
|
71
|
+
export declare const authorizePostHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<string | import("h3").HTTPResponse>>;
|
|
72
72
|
/**
|
|
73
73
|
* Token endpoint - exchange code for access token
|
|
74
74
|
* POST /token
|
|
@@ -77,7 +77,7 @@ export declare const authorizePostHandler: import("h3").EventHandler<import("h3"
|
|
|
77
77
|
* - authorization_code grant (with PKCE validation)
|
|
78
78
|
* - refresh_token grant
|
|
79
79
|
*/
|
|
80
|
-
export declare const tokenHandler: import("h3").
|
|
80
|
+
export declare const tokenHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
81
81
|
error: string;
|
|
82
82
|
error_description: string;
|
|
83
83
|
access_token?: undefined;
|
|
@@ -85,11 +85,11 @@ export declare const tokenHandler: import("h3").EventHandler<import("h3").EventH
|
|
|
85
85
|
expires_in?: undefined;
|
|
86
86
|
refresh_token?: undefined;
|
|
87
87
|
} | {
|
|
88
|
+
error?: undefined;
|
|
89
|
+
error_description?: undefined;
|
|
88
90
|
access_token: string;
|
|
89
91
|
token_type: string;
|
|
90
92
|
expires_in: number;
|
|
91
93
|
refresh_token: string;
|
|
92
|
-
error?: undefined;
|
|
93
|
-
error_description?: undefined;
|
|
94
94
|
}>>;
|
|
95
95
|
//# sourceMappingURL=oauth.d.ts.map
|
package/dist/oauth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;EAyB/B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;GAoC1B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,0GA+C9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,mHA0D/B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;GA2FvB,CAAC"}
|
package/dist/oauth.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
+
//#region src/oauth.ts
|
|
5
6
|
/**
|
|
6
7
|
* OAuth 2.0 endpoints for Claude Desktop integration
|
|
7
8
|
*
|
|
@@ -24,11 +25,11 @@ import { createHash } from "node:crypto";
|
|
|
24
25
|
*
|
|
25
26
|
* MCP clients MUST check this endpoint first for server capabilities.
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
-
const host = event.
|
|
29
|
-
const baseUrl = `${event.
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
var oauthMetadataHandler = defineHandler((event) => {
|
|
29
|
+
const host = event.req.headers.get("host") || "localhost:3000";
|
|
30
|
+
const baseUrl = `${event.req.headers.get("x-forwarded-proto") || "http"}://${host}`;
|
|
31
|
+
event.res.headers.set("Content-Type", "application/json");
|
|
32
|
+
event.res.headers.set("Cache-Control", "public, max-age=3600");
|
|
32
33
|
return {
|
|
33
34
|
issuer: baseUrl,
|
|
34
35
|
authorization_endpoint: `${baseUrl}/authorize`,
|
|
@@ -50,13 +51,13 @@ const oauthMetadataHandler = defineEventHandler((event) => {
|
|
|
50
51
|
* Since we use stateless tokens, we accept any registration and return
|
|
51
52
|
* a generated client_id.
|
|
52
53
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
var registerHandler = defineHandler(async (event) => {
|
|
55
|
+
event.res.headers.set("Content-Type", "application/json");
|
|
55
56
|
let body;
|
|
56
57
|
try {
|
|
57
|
-
body = await
|
|
58
|
+
body = await event.req.json();
|
|
58
59
|
} catch {
|
|
59
|
-
event.
|
|
60
|
+
event.res.status = 400;
|
|
60
61
|
return {
|
|
61
62
|
error: "invalid_request",
|
|
62
63
|
error_description: "Invalid JSON body"
|
|
@@ -68,7 +69,7 @@ const registerHandler = defineEventHandler(async (event) => {
|
|
|
68
69
|
name: clientName,
|
|
69
70
|
ts: Date.now()
|
|
70
71
|
})).toString("base64url");
|
|
71
|
-
event.
|
|
72
|
+
event.res.status = 201;
|
|
72
73
|
return {
|
|
73
74
|
client_id: clientId,
|
|
74
75
|
client_name: clientName,
|
|
@@ -82,7 +83,7 @@ const registerHandler = defineEventHandler(async (event) => {
|
|
|
82
83
|
* Authorization endpoint - shows login form
|
|
83
84
|
* GET /authorize
|
|
84
85
|
*/
|
|
85
|
-
|
|
86
|
+
var authorizeGetHandler = defineHandler((event) => {
|
|
86
87
|
const query = getQuery(event);
|
|
87
88
|
const clientId = query.client_id;
|
|
88
89
|
const redirectUri = query.redirect_uri;
|
|
@@ -91,8 +92,8 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
91
92
|
const codeChallengeMethod = query.code_challenge_method;
|
|
92
93
|
const scope = query.scope;
|
|
93
94
|
if (!redirectUri) {
|
|
94
|
-
|
|
95
|
-
event.
|
|
95
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
96
|
+
event.res.status = 400;
|
|
96
97
|
return renderErrorPage("Missing required parameter: redirect_uri");
|
|
97
98
|
}
|
|
98
99
|
if (!codeChallenge) {
|
|
@@ -100,16 +101,16 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
100
101
|
errorUrl.searchParams.set("error", "invalid_request");
|
|
101
102
|
errorUrl.searchParams.set("error_description", "code_challenge is required");
|
|
102
103
|
if (state) errorUrl.searchParams.set("state", state);
|
|
103
|
-
return
|
|
104
|
+
return redirect(errorUrl.toString());
|
|
104
105
|
}
|
|
105
106
|
if (codeChallengeMethod && codeChallengeMethod !== "S256") {
|
|
106
107
|
const errorUrl = new URL(redirectUri);
|
|
107
108
|
errorUrl.searchParams.set("error", "invalid_request");
|
|
108
109
|
errorUrl.searchParams.set("error_description", "Only S256 code_challenge_method is supported");
|
|
109
110
|
if (state) errorUrl.searchParams.set("state", state);
|
|
110
|
-
return
|
|
111
|
+
return redirect(errorUrl.toString());
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
113
114
|
return renderLoginForm({
|
|
114
115
|
clientId,
|
|
115
116
|
redirectUri,
|
|
@@ -123,11 +124,12 @@ const authorizeGetHandler = defineEventHandler((event) => {
|
|
|
123
124
|
* Authorization endpoint - process login
|
|
124
125
|
* POST /authorize
|
|
125
126
|
*/
|
|
126
|
-
|
|
127
|
-
const
|
|
127
|
+
var authorizePostHandler = defineHandler(async (event) => {
|
|
128
|
+
const formData = await event.req.formData();
|
|
129
|
+
const { orgId, apiToken, userId, redirectUri, state, codeChallenge, codeChallengeMethod } = Object.fromEntries(formData.entries());
|
|
128
130
|
if (!redirectUri) {
|
|
129
|
-
|
|
130
|
-
event.
|
|
131
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
132
|
+
event.res.status = 400;
|
|
131
133
|
return renderErrorPage("Missing redirect_uri parameter");
|
|
132
134
|
}
|
|
133
135
|
try {
|
|
@@ -135,15 +137,15 @@ const authorizePostHandler = defineEventHandler(async (event) => {
|
|
|
135
137
|
const isLocalhost = uri.hostname === "localhost" || uri.hostname === "127.0.0.1";
|
|
136
138
|
const isHttps = uri.protocol === "https:";
|
|
137
139
|
if (!isLocalhost && !isHttps) {
|
|
138
|
-
event.
|
|
140
|
+
event.res.status = 400;
|
|
139
141
|
return renderErrorPage("redirect_uri must be HTTPS or localhost");
|
|
140
142
|
}
|
|
141
143
|
} catch {
|
|
142
|
-
event.
|
|
144
|
+
event.res.status = 400;
|
|
143
145
|
return renderErrorPage("Invalid redirect_uri format");
|
|
144
146
|
}
|
|
145
147
|
if (!orgId || !apiToken) {
|
|
146
|
-
|
|
148
|
+
event.res.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
147
149
|
return renderLoginForm({
|
|
148
150
|
redirectUri,
|
|
149
151
|
state,
|
|
@@ -162,8 +164,7 @@ const authorizePostHandler = defineEventHandler(async (event) => {
|
|
|
162
164
|
const redirectUrl = new URL(redirectUri);
|
|
163
165
|
redirectUrl.searchParams.set("code", code);
|
|
164
166
|
if (state) redirectUrl.searchParams.set("state", state);
|
|
165
|
-
|
|
166
|
-
return renderSuccessPage(redirectUrl.toString());
|
|
167
|
+
return redirect(redirectUrl.toString());
|
|
167
168
|
});
|
|
168
169
|
/**
|
|
169
170
|
* Token endpoint - exchange code for access token
|
|
@@ -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
|
-
|
|
177
|
-
|
|
177
|
+
var 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"
|
|
@@ -457,115 +457,6 @@ function renderLoginForm(params) {
|
|
|
457
457
|
</html>`;
|
|
458
458
|
}
|
|
459
459
|
/**
|
|
460
|
-
* Render success page with auto-redirect
|
|
461
|
-
*/
|
|
462
|
-
function renderSuccessPage(redirectUrl) {
|
|
463
|
-
return `<!DOCTYPE html>
|
|
464
|
-
<html lang="en">
|
|
465
|
-
<head>
|
|
466
|
-
<meta charset="UTF-8">
|
|
467
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
468
|
-
<meta http-equiv="refresh" content="2;url=${escapeHtml(redirectUrl)}">
|
|
469
|
-
<title>Connected - Productive MCP</title>
|
|
470
|
-
<style>
|
|
471
|
-
* {
|
|
472
|
-
box-sizing: border-box;
|
|
473
|
-
margin: 0;
|
|
474
|
-
padding: 0;
|
|
475
|
-
}
|
|
476
|
-
body {
|
|
477
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
478
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
479
|
-
min-height: 100vh;
|
|
480
|
-
display: flex;
|
|
481
|
-
align-items: center;
|
|
482
|
-
justify-content: center;
|
|
483
|
-
padding: 20px;
|
|
484
|
-
}
|
|
485
|
-
.container {
|
|
486
|
-
background: white;
|
|
487
|
-
border-radius: 16px;
|
|
488
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
489
|
-
padding: 40px;
|
|
490
|
-
width: 100%;
|
|
491
|
-
max-width: 420px;
|
|
492
|
-
text-align: center;
|
|
493
|
-
}
|
|
494
|
-
.success-icon {
|
|
495
|
-
width: 64px;
|
|
496
|
-
height: 64px;
|
|
497
|
-
margin: 0 auto 24px;
|
|
498
|
-
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
499
|
-
border-radius: 50%;
|
|
500
|
-
display: flex;
|
|
501
|
-
align-items: center;
|
|
502
|
-
justify-content: center;
|
|
503
|
-
}
|
|
504
|
-
.success-icon svg {
|
|
505
|
-
width: 32px;
|
|
506
|
-
height: 32px;
|
|
507
|
-
stroke: white;
|
|
508
|
-
}
|
|
509
|
-
h1 {
|
|
510
|
-
color: #1a1a2e;
|
|
511
|
-
font-size: 24px;
|
|
512
|
-
margin-bottom: 8px;
|
|
513
|
-
}
|
|
514
|
-
.message {
|
|
515
|
-
color: #666;
|
|
516
|
-
font-size: 14px;
|
|
517
|
-
margin-bottom: 24px;
|
|
518
|
-
}
|
|
519
|
-
.spinner {
|
|
520
|
-
width: 24px;
|
|
521
|
-
height: 24px;
|
|
522
|
-
border: 3px solid #e5e7eb;
|
|
523
|
-
border-top-color: #667eea;
|
|
524
|
-
border-radius: 50%;
|
|
525
|
-
animation: spin 1s linear infinite;
|
|
526
|
-
margin: 0 auto 16px;
|
|
527
|
-
}
|
|
528
|
-
@keyframes spin {
|
|
529
|
-
to { transform: rotate(360deg); }
|
|
530
|
-
}
|
|
531
|
-
.redirect-text {
|
|
532
|
-
color: #9ca3af;
|
|
533
|
-
font-size: 13px;
|
|
534
|
-
margin-bottom: 16px;
|
|
535
|
-
}
|
|
536
|
-
.manual-link {
|
|
537
|
-
color: #667eea;
|
|
538
|
-
text-decoration: none;
|
|
539
|
-
font-size: 14px;
|
|
540
|
-
}
|
|
541
|
-
.manual-link:hover {
|
|
542
|
-
text-decoration: underline;
|
|
543
|
-
}
|
|
544
|
-
</style>
|
|
545
|
-
</head>
|
|
546
|
-
<body>
|
|
547
|
-
<div class="container">
|
|
548
|
-
<div class="success-icon">
|
|
549
|
-
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
550
|
-
<path d="M20 6L9 17L4 12" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
551
|
-
</svg>
|
|
552
|
-
</div>
|
|
553
|
-
<h1>Successfully Connected!</h1>
|
|
554
|
-
<p class="message">Your Productive.io credentials have been verified.</p>
|
|
555
|
-
<div class="spinner"></div>
|
|
556
|
-
<p class="redirect-text">Redirecting to Claude Desktop...</p>
|
|
557
|
-
<a href="${escapeHtml(redirectUrl)}" class="manual-link">Click here if not redirected automatically</a>
|
|
558
|
-
</div>
|
|
559
|
-
<script>
|
|
560
|
-
// Redirect after a short delay (backup for meta refresh)
|
|
561
|
-
setTimeout(function() {
|
|
562
|
-
window.location.href = ${JSON.stringify(redirectUrl)};
|
|
563
|
-
}, 2000);
|
|
564
|
-
<\/script>
|
|
565
|
-
</body>
|
|
566
|
-
</html>`;
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
460
|
* Render error page
|
|
570
461
|
*/
|
|
571
462
|
function renderErrorPage(message) {
|
|
@@ -616,6 +507,7 @@ function renderErrorPage(message) {
|
|
|
616
507
|
function escapeHtml(str) {
|
|
617
508
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
618
509
|
}
|
|
510
|
+
//#endregion
|
|
619
511
|
export { authorizeGetHandler, authorizePostHandler, oauthMetadataHandler, registerHandler, tokenHandler };
|
|
620
512
|
|
|
621
513
|
//# sourceMappingURL=oauth.js.map
|