@studiometa/productive-mcp 0.4.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +7 -0
- package/README.md +160 -61
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/crypto.js +1 -1
- package/dist/crypto.js.map +1 -1
- package/dist/formatters.d.ts +38 -8
- package/dist/formatters.d.ts.map +1 -1
- package/dist/handlers/bookings.d.ts +6 -0
- package/dist/handlers/bookings.d.ts.map +1 -0
- package/dist/handlers/comments.d.ts +6 -0
- package/dist/handlers/comments.d.ts.map +1 -0
- package/dist/handlers/companies.d.ts +6 -0
- package/dist/handlers/companies.d.ts.map +1 -0
- package/dist/handlers/deals.d.ts +6 -0
- package/dist/handlers/deals.d.ts.map +1 -0
- package/dist/handlers/index.d.ts +15 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/people.d.ts +7 -0
- package/dist/handlers/people.d.ts.map +1 -0
- package/dist/handlers/projects.d.ts +6 -0
- package/dist/handlers/projects.d.ts.map +1 -0
- package/dist/handlers/services.d.ts +6 -0
- package/dist/handlers/services.d.ts.map +1 -0
- package/dist/handlers/tasks.d.ts +6 -0
- package/dist/handlers/tasks.d.ts.map +1 -0
- package/dist/handlers/time.d.ts +6 -0
- package/dist/handlers/time.d.ts.map +1 -0
- package/dist/handlers/timers.d.ts +6 -0
- package/dist/handlers/timers.d.ts.map +1 -0
- package/dist/handlers/types.d.ts +78 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/utils.d.ts +17 -0
- package/dist/handlers/utils.d.ts.map +1 -0
- package/dist/handlers.d.ts +3 -12
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +2 -135
- package/dist/handlers.js.map +1 -1
- package/dist/http.js +3 -3
- package/dist/http.js.map +1 -1
- package/dist/index-CmTDkz-y.js +480 -0
- package/dist/index-CmTDkz-y.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth.d.ts +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +111 -12
- package/dist/oauth.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +1 -1
- package/dist/stdio.js.map +1 -1
- package/dist/tools.d.ts +3 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +55 -193
- package/dist/tools.js.map +1 -1
- package/dist/version-BmTJXDFu.js +5 -0
- package/dist/{version-uWLfG4Z0.js.map → version-BmTJXDFu.js.map} +1 -1
- package/package.json +45 -45
- package/dist/version-uWLfG4Z0.js +0 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/handlers/time.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKzE,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CAyCrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.d.ts","sourceRoot":"","sources":["../../src/handlers/timers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKxE,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,CA8BrB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for resource handlers
|
|
3
|
+
*/
|
|
4
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import type { ProductiveApi } from '@studiometa/productive-cli';
|
|
6
|
+
import type { McpFormatOptions } from '../formatters.js';
|
|
7
|
+
export type ToolResult = CallToolResult;
|
|
8
|
+
/**
|
|
9
|
+
* Context passed to each resource handler
|
|
10
|
+
*/
|
|
11
|
+
export interface HandlerContext {
|
|
12
|
+
api: ProductiveApi;
|
|
13
|
+
formatOptions: McpFormatOptions;
|
|
14
|
+
filter?: Record<string, string>;
|
|
15
|
+
page?: number;
|
|
16
|
+
perPage: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Common args shared across resources
|
|
20
|
+
*/
|
|
21
|
+
export interface CommonArgs {
|
|
22
|
+
id?: string;
|
|
23
|
+
person_id?: string;
|
|
24
|
+
service_id?: string;
|
|
25
|
+
task_id?: string;
|
|
26
|
+
company_id?: string;
|
|
27
|
+
time?: number;
|
|
28
|
+
date?: string;
|
|
29
|
+
note?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Task-specific args
|
|
33
|
+
*/
|
|
34
|
+
export interface TaskArgs extends CommonArgs {
|
|
35
|
+
title?: string;
|
|
36
|
+
project_id?: string;
|
|
37
|
+
task_list_id?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
assignee_id?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Comment-specific args
|
|
43
|
+
*/
|
|
44
|
+
export interface CommentArgs extends CommonArgs {
|
|
45
|
+
body?: string;
|
|
46
|
+
deal_id?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Timer-specific args
|
|
50
|
+
*/
|
|
51
|
+
export interface TimerArgs extends CommonArgs {
|
|
52
|
+
time_entry_id?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Deal-specific args
|
|
56
|
+
*/
|
|
57
|
+
export interface DealArgs extends CommonArgs {
|
|
58
|
+
name?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Booking-specific args
|
|
62
|
+
*/
|
|
63
|
+
export interface BookingArgs extends CommonArgs {
|
|
64
|
+
started_on?: string;
|
|
65
|
+
ended_on?: string;
|
|
66
|
+
event_id?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Company-specific args
|
|
70
|
+
*/
|
|
71
|
+
export interface CompanyArgs extends CommonArgs {
|
|
72
|
+
name?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resource handler function signature
|
|
76
|
+
*/
|
|
77
|
+
export type ResourceHandler<T extends CommonArgs = CommonArgs> = (action: string, args: T, ctx: HandlerContext) => Promise<ToolResult>;
|
|
78
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/handlers/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,CAAC;IACnB,aAAa,EAAE,gBAAgB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAC/D,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,CAAC,EACP,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for resource handlers
|
|
3
|
+
*/
|
|
4
|
+
import type { ToolResult } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Helper to create a successful JSON response
|
|
7
|
+
*/
|
|
8
|
+
export declare function jsonResult(data: unknown): ToolResult;
|
|
9
|
+
/**
|
|
10
|
+
* Helper to create an error response
|
|
11
|
+
*/
|
|
12
|
+
export declare function errorResult(message: string): ToolResult;
|
|
13
|
+
/**
|
|
14
|
+
* Convert unknown filter to string filter for API
|
|
15
|
+
*/
|
|
16
|
+
export declare function toStringFilter(filter?: Record<string, unknown>): Record<string, string> | undefined;
|
|
17
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/handlers/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAIpD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAKvD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CASpC"}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool execution handlers for Productive MCP server
|
|
3
|
-
* These are shared between stdio and HTTP transports
|
|
4
|
-
*/
|
|
5
|
-
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
-
import type { ProductiveCredentials } from './auth.js';
|
|
7
|
-
export type ToolResult = CallToolResult;
|
|
8
|
-
/**
|
|
9
|
-
* Execute a tool with the given credentials and arguments
|
|
10
3
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @param credentials - Productive API credentials
|
|
14
|
-
* @returns Tool execution result
|
|
4
|
+
* This module re-exports from the handlers/ directory for backwards compatibility.
|
|
5
|
+
* The handlers have been refactored into separate modules for better maintainability.
|
|
15
6
|
*/
|
|
16
|
-
export
|
|
7
|
+
export { executeToolWithCredentials, type ToolResult } from './handlers/index.js';
|
|
17
8
|
//# sourceMappingURL=handlers.d.ts.map
|
package/dist/handlers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,0BAA0B,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/handlers.js
CHANGED
|
@@ -1,138 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const MCP_FORMAT_OPTIONS = {
|
|
3
|
-
includeRelationshipIds: false,
|
|
4
|
-
includeTimestamps: false,
|
|
5
|
-
stripHtml: true
|
|
6
|
-
};
|
|
7
|
-
function formatTimeEntry(entry, _included) {
|
|
8
|
-
return formatTimeEntry$1(entry, MCP_FORMAT_OPTIONS);
|
|
9
|
-
}
|
|
10
|
-
function formatProject(project, _included) {
|
|
11
|
-
return formatProject$1(project, MCP_FORMAT_OPTIONS);
|
|
12
|
-
}
|
|
13
|
-
function formatTask(task, included) {
|
|
14
|
-
return formatTask$1(task, { ...MCP_FORMAT_OPTIONS, included });
|
|
15
|
-
}
|
|
16
|
-
function formatPerson(person, _included) {
|
|
17
|
-
return formatPerson$1(person, MCP_FORMAT_OPTIONS);
|
|
18
|
-
}
|
|
19
|
-
function formatService(service, _included) {
|
|
20
|
-
return formatService$1(service, MCP_FORMAT_OPTIONS);
|
|
21
|
-
}
|
|
22
|
-
function formatListResponse(data, formatter, meta, included) {
|
|
23
|
-
const wrappedFormatter = (item, _options) => {
|
|
24
|
-
return formatter(item, included);
|
|
25
|
-
};
|
|
26
|
-
const result = formatListResponse$1(data, wrappedFormatter, meta, {
|
|
27
|
-
...MCP_FORMAT_OPTIONS,
|
|
28
|
-
included
|
|
29
|
-
});
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
function jsonResult(data) {
|
|
33
|
-
return {
|
|
34
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function errorResult(message) {
|
|
38
|
-
return {
|
|
39
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
40
|
-
isError: true
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
async function executeToolWithCredentials(name, args, credentials) {
|
|
44
|
-
const api = new ProductiveApi({
|
|
45
|
-
token: credentials.apiToken,
|
|
46
|
-
"org-id": credentials.organizationId,
|
|
47
|
-
"user-id": credentials.userId
|
|
48
|
-
});
|
|
49
|
-
try {
|
|
50
|
-
switch (name) {
|
|
51
|
-
case "productive_list_projects": {
|
|
52
|
-
const result = await api.getProjects(args);
|
|
53
|
-
return jsonResult(formatListResponse(result.data, formatProject, result.meta));
|
|
54
|
-
}
|
|
55
|
-
case "productive_get_project": {
|
|
56
|
-
const result = await api.getProject(args.id);
|
|
57
|
-
return jsonResult(formatProject(result.data));
|
|
58
|
-
}
|
|
59
|
-
case "productive_list_time_entries": {
|
|
60
|
-
const result = await api.getTimeEntries(args);
|
|
61
|
-
return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta));
|
|
62
|
-
}
|
|
63
|
-
case "productive_get_time_entry": {
|
|
64
|
-
const result = await api.getTimeEntry(args.id);
|
|
65
|
-
return jsonResult(formatTimeEntry(result.data));
|
|
66
|
-
}
|
|
67
|
-
case "productive_create_time_entry": {
|
|
68
|
-
const result = await api.createTimeEntry(
|
|
69
|
-
args
|
|
70
|
-
);
|
|
71
|
-
return jsonResult({
|
|
72
|
-
success: true,
|
|
73
|
-
...formatTimeEntry(result.data)
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
case "productive_update_time_entry": {
|
|
77
|
-
const { id, ...data } = args;
|
|
78
|
-
const result = await api.updateTimeEntry(id, data);
|
|
79
|
-
return jsonResult({
|
|
80
|
-
success: true,
|
|
81
|
-
...formatTimeEntry(result.data)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
case "productive_delete_time_entry": {
|
|
85
|
-
await api.deleteTimeEntry(args.id);
|
|
86
|
-
return jsonResult({ success: true, message: "Time entry deleted" });
|
|
87
|
-
}
|
|
88
|
-
case "productive_list_tasks": {
|
|
89
|
-
const params = args || {};
|
|
90
|
-
params.include = ["project", "project.company"];
|
|
91
|
-
const result = await api.getTasks(params);
|
|
92
|
-
return jsonResult(formatListResponse(
|
|
93
|
-
result.data,
|
|
94
|
-
formatTask,
|
|
95
|
-
result.meta,
|
|
96
|
-
result.included
|
|
97
|
-
));
|
|
98
|
-
}
|
|
99
|
-
case "productive_get_task": {
|
|
100
|
-
const result = await api.getTask(args.id, {
|
|
101
|
-
include: ["project", "project.company"]
|
|
102
|
-
});
|
|
103
|
-
return jsonResult(formatTask(result.data, result.included));
|
|
104
|
-
}
|
|
105
|
-
case "productive_list_services": {
|
|
106
|
-
const result = await api.getServices(args);
|
|
107
|
-
return jsonResult(formatListResponse(result.data, formatService, result.meta));
|
|
108
|
-
}
|
|
109
|
-
case "productive_list_people": {
|
|
110
|
-
const result = await api.getPeople(args);
|
|
111
|
-
return jsonResult(formatListResponse(result.data, formatPerson, result.meta));
|
|
112
|
-
}
|
|
113
|
-
case "productive_get_person": {
|
|
114
|
-
const result = await api.getPerson(args.id);
|
|
115
|
-
return jsonResult(formatPerson(result.data));
|
|
116
|
-
}
|
|
117
|
-
case "productive_get_current_user": {
|
|
118
|
-
if (credentials.userId) {
|
|
119
|
-
const result = await api.getPerson(credentials.userId);
|
|
120
|
-
return jsonResult(formatPerson(result.data));
|
|
121
|
-
}
|
|
122
|
-
return jsonResult({
|
|
123
|
-
message: "User ID not configured. Set userId in credentials to use this tool.",
|
|
124
|
-
organizationId: credentials.organizationId
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
default:
|
|
128
|
-
return errorResult(`Unknown tool: ${name}`);
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
-
return errorResult(message);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
1
|
+
import { e } from "./index-CmTDkz-y.js";
|
|
135
2
|
export {
|
|
136
|
-
executeToolWithCredentials
|
|
3
|
+
e as executeToolWithCredentials
|
|
137
4
|
};
|
|
138
5
|
//# sourceMappingURL=handlers.js.map
|
package/dist/handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sources":["../src/formatters.ts","../src/handlers.ts"],"sourcesContent":["/**\n * Response formatters for agent-friendly output\n *\n * This module re-exports formatters from @studiometa/productive-cli\n * with MCP-specific defaults (no relationship IDs, no timestamps).\n */\n\nimport {\n formatTimeEntry as cliFormatTimeEntry,\n formatProject as cliFormatProject,\n formatTask as cliFormatTask,\n formatPerson as cliFormatPerson,\n formatService as cliFormatService,\n formatListResponse as cliFormatListResponse,\n type JsonApiResource,\n type JsonApiMeta,\n type FormatOptions,\n type FormattedPagination,\n} from '@studiometa/productive-cli';\n\n// Re-export types\nexport type { JsonApiResource, JsonApiMeta, FormatOptions, FormattedPagination };\n\n/**\n * MCP-specific format options\n * - No relationship IDs (cleaner output for agents)\n * - No timestamps (reduce noise)\n * - HTML stripping enabled\n */\nconst MCP_FORMAT_OPTIONS: FormatOptions = {\n includeRelationshipIds: false,\n includeTimestamps: false,\n stripHtml: true,\n};\n\n/**\n * Format time entry for agent consumption\n */\nexport function formatTimeEntry(\n entry: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatTimeEntry(entry, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format project for agent consumption\n */\nexport function formatProject(\n project: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatProject(project, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format task for agent consumption\n * Tasks use included resources to resolve project/company names\n */\nexport function formatTask(\n task: JsonApiResource,\n included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatTask(task, { ...MCP_FORMAT_OPTIONS, included });\n}\n\n/**\n * Format person for agent consumption\n */\nexport function formatPerson(\n person: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatPerson(person, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format service for agent consumption\n */\nexport function formatService(\n service: JsonApiResource,\n _included?: JsonApiResource[]\n): Record<string, unknown> {\n return cliFormatService(service, MCP_FORMAT_OPTIONS);\n}\n\n/**\n * Format list response with pagination\n *\n * @param data - Array of JSON:API resources\n * @param formatter - Formatter function (item, included?) => T\n * @param meta - Pagination metadata\n * @param included - Included resources for relationship resolution\n */\nexport function formatListResponse<T>(\n data: JsonApiResource[],\n formatter: (item: JsonApiResource, included?: JsonApiResource[]) => T,\n meta?: JsonApiMeta,\n included?: JsonApiResource[]\n): { data: T[]; meta?: FormattedPagination } {\n // Create a wrapper that converts (item, options?) signature to (item, included?) signature\n const wrappedFormatter = (item: JsonApiResource, _options?: FormatOptions) => {\n return formatter(item, included);\n };\n\n const result = cliFormatListResponse(data, wrappedFormatter, meta, {\n ...MCP_FORMAT_OPTIONS,\n included,\n });\n\n return result as { data: T[]; meta?: FormattedPagination };\n}\n","/**\n * Tool execution handlers for Productive MCP server\n * These are shared between stdio and HTTP transports\n */\n\nimport { ProductiveApi } from '@studiometa/productive-cli';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { ProductiveCredentials } from './auth.js';\nimport {\n formatTimeEntry,\n formatTask,\n formatProject,\n formatPerson,\n formatService,\n formatListResponse,\n} from './formatters.js';\n\nexport type ToolResult = CallToolResult;\n\n/**\n * Helper to create a successful JSON response\n */\nfunction jsonResult(data: unknown): ToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n}\n\n/**\n * Helper to create an error response\n */\nfunction errorResult(message: string): ToolResult {\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n}\n\n/**\n * Execute a tool with the given credentials and arguments\n *\n * @param name - Tool name\n * @param args - Tool arguments\n * @param credentials - Productive API credentials\n * @returns Tool execution result\n */\nexport async function executeToolWithCredentials(\n name: string,\n args: Record<string, unknown>,\n credentials: ProductiveCredentials\n): Promise<ToolResult> {\n // Initialize API client with provided credentials\n // Note: getConfig expects CLI-style keys (token, org-id) not camelCase\n const api = new ProductiveApi({\n token: credentials.apiToken,\n 'org-id': credentials.organizationId,\n 'user-id': credentials.userId,\n } as Record<string, string>);\n\n try {\n switch (name) {\n case 'productive_list_projects': {\n const result = await api.getProjects(args as Parameters<typeof api.getProjects>[0]);\n return jsonResult(formatListResponse(result.data, formatProject, result.meta));\n }\n\n case 'productive_get_project': {\n const result = await api.getProject((args as { id: string }).id);\n return jsonResult(formatProject(result.data));\n }\n\n case 'productive_list_time_entries': {\n const result = await api.getTimeEntries(args as Parameters<typeof api.getTimeEntries>[0]);\n return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta));\n }\n\n case 'productive_get_time_entry': {\n const result = await api.getTimeEntry((args as { id: string }).id);\n return jsonResult(formatTimeEntry(result.data));\n }\n\n case 'productive_create_time_entry': {\n const result = await api.createTimeEntry(\n args as Parameters<typeof api.createTimeEntry>[0]\n );\n return jsonResult({\n success: true,\n ...formatTimeEntry(result.data),\n });\n }\n\n case 'productive_update_time_entry': {\n const { id, ...data } = args as { id: string } & Record<string, unknown>;\n const result = await api.updateTimeEntry(id, data as Parameters<typeof api.updateTimeEntry>[1]);\n return jsonResult({\n success: true,\n ...formatTimeEntry(result.data),\n });\n }\n\n case 'productive_delete_time_entry': {\n await api.deleteTimeEntry((args as { id: string }).id);\n return jsonResult({ success: true, message: 'Time entry deleted' });\n }\n\n case 'productive_list_tasks': {\n const params = args as Parameters<typeof api.getTasks>[0] || {};\n // Always include project and company for context\n params.include = ['project', 'project.company'];\n const result = await api.getTasks(params);\n return jsonResult(formatListResponse(\n result.data,\n formatTask,\n result.meta,\n result.included\n ));\n }\n\n case 'productive_get_task': {\n const result = await api.getTask((args as { id: string }).id, {\n include: ['project', 'project.company'],\n });\n return jsonResult(formatTask(result.data, result.included));\n }\n\n case 'productive_list_services': {\n const result = await api.getServices(args as Parameters<typeof api.getServices>[0]);\n return jsonResult(formatListResponse(result.data, formatService, result.meta));\n }\n\n case 'productive_list_people': {\n const result = await api.getPeople(args as Parameters<typeof api.getPeople>[0]);\n return jsonResult(formatListResponse(result.data, formatPerson, result.meta));\n }\n\n case 'productive_get_person': {\n const result = await api.getPerson((args as { id: string }).id);\n return jsonResult(formatPerson(result.data));\n }\n\n case 'productive_get_current_user': {\n if (credentials.userId) {\n const result = await api.getPerson(credentials.userId);\n return jsonResult(formatPerson(result.data));\n }\n return jsonResult({\n message: 'User ID not configured. Set userId in credentials to use this tool.',\n organizationId: credentials.organizationId,\n });\n }\n\n default:\n return errorResult(`Unknown tool: ${name}`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return errorResult(message);\n }\n}\n"],"names":["cliFormatTimeEntry","cliFormatProject","cliFormatTask","cliFormatPerson","cliFormatService","cliFormatListResponse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handlers.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/dist/http.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createApp, createRouter, defineEventHandler, getHeader, setResponseHeader, readBody } from "h3";
|
|
2
|
-
import { TOOLS } from "./tools.js";
|
|
3
|
-
import { executeToolWithCredentials } from "./handlers.js";
|
|
4
2
|
import { parseAuthHeader } from "./auth.js";
|
|
5
|
-
import {
|
|
3
|
+
import { e as executeToolWithCredentials } from "./index-CmTDkz-y.js";
|
|
6
4
|
import { oauthMetadataHandler, registerHandler, authorizeGetHandler, authorizePostHandler, tokenHandler } from "./oauth.js";
|
|
5
|
+
import { TOOLS } from "./tools.js";
|
|
6
|
+
import { V as VERSION } from "./version-BmTJXDFu.js";
|
|
7
7
|
function jsonRpcError(code, message, id = null) {
|
|
8
8
|
return {
|
|
9
9
|
jsonrpc: "2.0",
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sources":["../src/http.ts"],"sourcesContent":["/**\n * HTTP transport handlers for Productive MCP Server\n *\n * This module contains the app/router creation logic for the HTTP transport.\n * The actual server startup is in server.ts.\n */\n\nimport {\n createApp,\n createRouter,\n defineEventHandler,\n readBody,\n getHeader,\n setResponseHeader,\n type App,\n} from 'h3';\n\nimport {
|
|
1
|
+
{"version":3,"file":"http.js","sources":["../src/http.ts"],"sourcesContent":["/**\n * HTTP transport handlers for Productive MCP Server\n *\n * This module contains the app/router creation logic for the HTTP transport.\n * The actual server startup is in server.ts.\n */\n\nimport {\n createApp,\n createRouter,\n defineEventHandler,\n readBody,\n getHeader,\n setResponseHeader,\n type App,\n} from 'h3';\n\nimport { parseAuthHeader } from './auth.js';\nimport { executeToolWithCredentials } from './handlers.js';\nimport {\n oauthMetadataHandler,\n registerHandler,\n authorizeGetHandler,\n authorizePostHandler,\n tokenHandler,\n} from './oauth.js';\nimport { TOOLS } from './tools.js';\nimport { VERSION } from './version.js';\n\n/**\n * JSON-RPC error response\n */\nexport function jsonRpcError(code: number, message: string, id: string | number | null = null) {\n return {\n jsonrpc: '2.0',\n error: { code, message },\n id,\n };\n}\n\n/**\n * JSON-RPC success response\n */\nexport function jsonRpcSuccess(result: unknown, id: string | number | null = null) {\n return {\n jsonrpc: '2.0',\n result,\n id,\n };\n}\n\n/**\n * Handle the initialize JSON-RPC method\n */\nexport function handleInitialize() {\n return {\n protocolVersion: '2024-11-05',\n serverInfo: {\n name: 'productive-mcp',\n version: VERSION,\n },\n capabilities: {\n tools: {},\n },\n };\n}\n\n/**\n * Handle the tools/list JSON-RPC method\n */\nexport function handleToolsList() {\n return { tools: TOOLS };\n}\n\n/**\n * Create the h3 application with all routes\n */\nexport function createHttpApp(): App {\n const app = createApp();\n const router = createRouter();\n\n // OAuth 2.0 endpoints for Claude Desktop integration (MCP auth spec)\n router.get('/.well-known/oauth-authorization-server', oauthMetadataHandler);\n router.post('/register', registerHandler); // Dynamic Client Registration (RFC 7591)\n router.get('/authorize', authorizeGetHandler);\n router.post('/authorize', authorizePostHandler);\n router.post('/token', tokenHandler);\n\n // Health check endpoint\n router.get(\n '/',\n defineEventHandler(() => {\n return { status: 'ok', service: 'productive-mcp', version: VERSION };\n }),\n );\n\n router.get(\n '/health',\n defineEventHandler(() => {\n return { status: 'ok' };\n }),\n );\n\n // MCP endpoint - handles JSON-RPC over HTTP\n router.post(\n '/mcp',\n defineEventHandler(async (event) => {\n // Parse authorization header\n const authHeader = getHeader(event, 'authorization');\n const credentials = parseAuthHeader(authHeader);\n\n if (!credentials) {\n setResponseHeader(event, 'Content-Type', 'application/json');\n event.node.res.statusCode = 401;\n return jsonRpcError(\n -32001,\n 'Authentication required. Provide Bearer token with base64(organizationId:apiToken:userId)',\n );\n }\n\n setResponseHeader(event, 'Content-Type', 'application/json');\n\n // Parse JSON-RPC request\n let body: { method?: string; params?: unknown; id?: string | number };\n try {\n body = await readBody(event);\n } catch {\n event.node.res.statusCode = 400;\n return jsonRpcError(-32700, 'Parse error: Invalid JSON');\n }\n\n if (!body || typeof body !== 'object') {\n event.node.res.statusCode = 400;\n return jsonRpcError(-32700, 'Parse error: Invalid JSON');\n }\n\n const { method, params, id } = body;\n\n try {\n if (method === 'initialize') {\n return jsonRpcSuccess(handleInitialize(), id ?? null);\n }\n\n if (method === 'tools/list') {\n return jsonRpcSuccess(handleToolsList(), id ?? null);\n }\n\n if (method === 'tools/call') {\n const { name, arguments: args } = params as {\n name: string;\n arguments?: Record<string, unknown>;\n };\n const result = await executeToolWithCredentials(name, args || {}, credentials);\n return jsonRpcSuccess(result, id ?? null);\n }\n\n // Unknown method\n return jsonRpcError(-32601, `Method not found: ${method}`, id ?? null);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return jsonRpcError(-32603, `Internal error: ${message}`, id ?? null);\n }\n }),\n );\n\n // SSE endpoint for server-sent events (optional, for streaming responses)\n router.get(\n '/mcp/sse',\n defineEventHandler(async (event) => {\n const authHeader = getHeader(event, 'authorization');\n const credentials = parseAuthHeader(authHeader);\n\n if (!credentials) {\n event.node.res.statusCode = 401;\n return { error: 'Authentication required' };\n }\n\n // Set SSE headers\n setResponseHeader(event, 'Content-Type', 'text/event-stream');\n setResponseHeader(event, 'Cache-Control', 'no-cache');\n setResponseHeader(event, 'Connection', 'keep-alive');\n\n // Generate session ID and send it\n const sessionId = crypto.randomUUID();\n\n // Send initial session event\n event.node.res.write(`event: session\\ndata: ${JSON.stringify({ sessionId })}\\n\\n`);\n\n // Keep connection alive\n const keepAlive = setInterval(() => {\n event.node.res.write(': keepalive\\n\\n');\n }, 30000);\n\n // Clean up on close\n event.node.req.on('close', () => {\n clearInterval(keepAlive);\n });\n\n // Don't end the response - keep it open for SSE\n return new Promise(() => {});\n }),\n );\n\n app.use(router);\n return app;\n}\n"],"names":[],"mappings":"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"}
|