@studiometa/productive-mcp 0.3.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.
@@ -0,0 +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 const api = new ProductiveApi({\n apiToken: credentials.apiToken,\n organizationId: credentials.organizationId,\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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"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * HTTP transport handlers for Productive MCP Server
3
+ *
4
+ * This module contains the app/router creation logic for the HTTP transport.
5
+ * The actual server startup is in server.ts.
6
+ */
7
+ import { type App } from 'h3';
8
+ /**
9
+ * JSON-RPC error response
10
+ */
11
+ export declare function jsonRpcError(code: number, message: string, id?: string | number | null): {
12
+ jsonrpc: string;
13
+ error: {
14
+ code: number;
15
+ message: string;
16
+ };
17
+ id: string | number | null;
18
+ };
19
+ /**
20
+ * JSON-RPC success response
21
+ */
22
+ export declare function jsonRpcSuccess(result: unknown, id?: string | number | null): {
23
+ jsonrpc: string;
24
+ result: unknown;
25
+ id: string | number | null;
26
+ };
27
+ /**
28
+ * Handle the initialize JSON-RPC method
29
+ */
30
+ export declare function handleInitialize(): {
31
+ protocolVersion: string;
32
+ serverInfo: {
33
+ name: string;
34
+ version: string;
35
+ };
36
+ capabilities: {
37
+ tools: {};
38
+ };
39
+ };
40
+ /**
41
+ * Handle the tools/list JSON-RPC method
42
+ */
43
+ export declare function handleToolsList(): {
44
+ tools: {
45
+ inputSchema: {
46
+ [x: string]: unknown;
47
+ type: "object";
48
+ properties?: {
49
+ [x: string]: object;
50
+ } | undefined;
51
+ required?: string[] | undefined;
52
+ };
53
+ name: string;
54
+ description?: string | undefined;
55
+ outputSchema?: {
56
+ [x: string]: unknown;
57
+ type: "object";
58
+ properties?: {
59
+ [x: string]: object;
60
+ } | undefined;
61
+ required?: string[] | undefined;
62
+ } | undefined;
63
+ annotations?: {
64
+ title?: string | undefined;
65
+ readOnlyHint?: boolean | undefined;
66
+ destructiveHint?: boolean | undefined;
67
+ idempotentHint?: boolean | undefined;
68
+ openWorldHint?: boolean | undefined;
69
+ } | undefined;
70
+ execution?: {
71
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
72
+ } | undefined;
73
+ _meta?: {
74
+ [x: string]: unknown;
75
+ } | undefined;
76
+ icons?: {
77
+ src: string;
78
+ mimeType?: string | undefined;
79
+ sizes?: string[] | undefined;
80
+ theme?: "light" | "dark" | undefined;
81
+ }[] | undefined;
82
+ title?: string | undefined;
83
+ }[];
84
+ };
85
+ /**
86
+ * Create the h3 application with all routes
87
+ */
88
+ export declare function createHttpApp(): App;
89
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAOL,KAAK,GAAG,EACT,MAAM,IAAI,CAAC;AAMZ;;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;;;;;;;;;EAW/B;AAED;;GAEG;AACH,wBAAgB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE9B;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAyHnC"}
package/dist/http.js ADDED
@@ -0,0 +1,131 @@
1
+ import { createApp, createRouter, defineEventHandler, getHeader, setResponseHeader, readBody } from "h3";
2
+ import { TOOLS } from "./tools.js";
3
+ import { executeToolWithCredentials } from "./handlers.js";
4
+ import { parseAuthHeader } from "./auth.js";
5
+ function jsonRpcError(code, message, id = null) {
6
+ return {
7
+ jsonrpc: "2.0",
8
+ error: { code, message },
9
+ id
10
+ };
11
+ }
12
+ function jsonRpcSuccess(result, id = null) {
13
+ return {
14
+ jsonrpc: "2.0",
15
+ result,
16
+ id
17
+ };
18
+ }
19
+ function handleInitialize() {
20
+ return {
21
+ protocolVersion: "2024-11-05",
22
+ serverInfo: {
23
+ name: "productive-mcp",
24
+ version: "0.1.0"
25
+ },
26
+ capabilities: {
27
+ tools: {}
28
+ }
29
+ };
30
+ }
31
+ function handleToolsList() {
32
+ return { tools: TOOLS };
33
+ }
34
+ function createHttpApp() {
35
+ const app = createApp();
36
+ const router = createRouter();
37
+ router.get(
38
+ "/",
39
+ defineEventHandler(() => {
40
+ return { status: "ok", service: "productive-mcp", version: "0.1.0" };
41
+ })
42
+ );
43
+ router.get(
44
+ "/health",
45
+ defineEventHandler(() => {
46
+ return { status: "ok" };
47
+ })
48
+ );
49
+ router.post(
50
+ "/mcp",
51
+ defineEventHandler(async (event) => {
52
+ const authHeader = getHeader(event, "authorization");
53
+ const credentials = parseAuthHeader(authHeader);
54
+ if (!credentials) {
55
+ setResponseHeader(event, "Content-Type", "application/json");
56
+ event.node.res.statusCode = 401;
57
+ return jsonRpcError(
58
+ -32001,
59
+ "Authentication required. Provide Bearer token with base64(organizationId:apiToken:userId)"
60
+ );
61
+ }
62
+ setResponseHeader(event, "Content-Type", "application/json");
63
+ let body;
64
+ try {
65
+ body = await readBody(event);
66
+ } catch {
67
+ event.node.res.statusCode = 400;
68
+ return jsonRpcError(-32700, "Parse error: Invalid JSON");
69
+ }
70
+ if (!body || typeof body !== "object") {
71
+ event.node.res.statusCode = 400;
72
+ return jsonRpcError(-32700, "Parse error: Invalid JSON");
73
+ }
74
+ const { method, params, id } = body;
75
+ try {
76
+ if (method === "initialize") {
77
+ return jsonRpcSuccess(handleInitialize(), id ?? null);
78
+ }
79
+ if (method === "tools/list") {
80
+ return jsonRpcSuccess(handleToolsList(), id ?? null);
81
+ }
82
+ if (method === "tools/call") {
83
+ const { name, arguments: args } = params;
84
+ const result = await executeToolWithCredentials(name, args || {}, credentials);
85
+ return jsonRpcSuccess(result, id ?? null);
86
+ }
87
+ return jsonRpcError(-32601, `Method not found: ${method}`, id ?? null);
88
+ } catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ return jsonRpcError(-32603, `Internal error: ${message}`, id ?? null);
91
+ }
92
+ })
93
+ );
94
+ router.get(
95
+ "/mcp/sse",
96
+ defineEventHandler(async (event) => {
97
+ const authHeader = getHeader(event, "authorization");
98
+ const credentials = parseAuthHeader(authHeader);
99
+ if (!credentials) {
100
+ event.node.res.statusCode = 401;
101
+ return { error: "Authentication required" };
102
+ }
103
+ setResponseHeader(event, "Content-Type", "text/event-stream");
104
+ setResponseHeader(event, "Cache-Control", "no-cache");
105
+ setResponseHeader(event, "Connection", "keep-alive");
106
+ const sessionId = crypto.randomUUID();
107
+ event.node.res.write(`event: session
108
+ data: ${JSON.stringify({ sessionId })}
109
+
110
+ `);
111
+ const keepAlive = setInterval(() => {
112
+ event.node.res.write(": keepalive\n\n");
113
+ }, 3e4);
114
+ event.node.req.on("close", () => {
115
+ clearInterval(keepAlive);
116
+ });
117
+ return new Promise(() => {
118
+ });
119
+ })
120
+ );
121
+ app.use(router);
122
+ return app;
123
+ }
124
+ export {
125
+ createHttpApp,
126
+ handleInitialize,
127
+ handleToolsList,
128
+ jsonRpcError,
129
+ jsonRpcSuccess
130
+ };
131
+ //# sourceMappingURL=http.js.map
@@ -0,0 +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 { TOOLS } from './tools.js';\nimport { executeToolWithCredentials } from './handlers.js';\nimport { parseAuthHeader } from './auth.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: '0.1.0',\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 // Health check endpoint\n router.get(\n '/',\n defineEventHandler(() => {\n return { status: 'ok', service: 'productive-mcp', version: '0.1.0' };\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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"}
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Productive MCP Server - Stdio Transport
4
+ *
5
+ * This is the local execution mode using stdio transport.
6
+ * For remote HTTP deployment, use server.ts instead.
7
+ *
8
+ * Usage:
9
+ * npx @studiometa/productive-mcp
10
+ *
11
+ * Or in Claude Desktop config:
12
+ * {
13
+ * "mcpServers": {
14
+ * "productive": {
15
+ * "command": "npx",
16
+ * "args": ["@studiometa/productive-mcp"]
17
+ * }
18
+ * }
19
+ * }
20
+ */
21
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
22
+ /**
23
+ * Create and configure the MCP server
24
+ */
25
+ export declare function createStdioServer(): Server;
26
+ /**
27
+ * Start the stdio server
28
+ */
29
+ export declare function startStdioServer(): Promise<void>;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAgBnE;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CA6C1C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKtD"}
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { getAvailableTools, getAvailablePrompts, handlePrompt, handleToolCall } from "./stdio.js";
6
+ function createStdioServer() {
7
+ const server = new Server(
8
+ {
9
+ name: "productive-mcp",
10
+ version: "0.1.0"
11
+ },
12
+ {
13
+ capabilities: {
14
+ tools: {},
15
+ prompts: {}
16
+ }
17
+ }
18
+ );
19
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
20
+ return { tools: getAvailableTools() };
21
+ });
22
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
23
+ return { prompts: getAvailablePrompts() };
24
+ });
25
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
26
+ return handlePrompt(request.params.name);
27
+ });
28
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29
+ const { name, arguments: args } = request.params;
30
+ try {
31
+ return await handleToolCall(name, args || {});
32
+ } catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ return {
35
+ content: [{ type: "text", text: `Error: ${message}` }],
36
+ isError: true
37
+ };
38
+ }
39
+ });
40
+ return server;
41
+ }
42
+ async function startStdioServer() {
43
+ const server = createStdioServer();
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ console.error("Productive MCP server running on stdio");
47
+ }
48
+ const isMainModule = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/productive-mcp") || process.argv[1]?.endsWith("\\productive-mcp");
49
+ if (isMainModule) {
50
+ startStdioServer().catch((error) => {
51
+ console.error("Fatal error:", error);
52
+ process.exit(1);
53
+ });
54
+ }
55
+ export {
56
+ createStdioServer,
57
+ startStdioServer
58
+ };
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Productive MCP Server - Stdio Transport\n *\n * This is the local execution mode using stdio transport.\n * For remote HTTP deployment, use server.ts instead.\n *\n * Usage:\n * npx @studiometa/productive-mcp\n *\n * Or in Claude Desktop config:\n * {\n * \"mcpServers\": {\n * \"productive\": {\n * \"command\": \"npx\",\n * \"args\": [\"@studiometa/productive-mcp\"]\n * }\n * }\n * }\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n ListPromptsRequestSchema,\n GetPromptRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport {\n getAvailableTools,\n getAvailablePrompts,\n handleToolCall,\n handlePrompt,\n} from './stdio.js';\n\n/**\n * Create and configure the MCP server\n */\nexport function createStdioServer(): Server {\n const server = new Server(\n {\n name: 'productive-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n prompts: {},\n },\n }\n );\n\n // List available tools (including stdio-only configuration tools)\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: getAvailableTools() };\n });\n\n // List available prompts\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return { prompts: getAvailablePrompts() };\n });\n\n // Get prompt\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n return handlePrompt(request.params.name);\n });\n\n // Handle tool calls\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n return await handleToolCall(name, (args as Record<string, unknown>) || {});\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Error: ${message}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Start the stdio server\n */\nexport async function startStdioServer(): Promise<void> {\n const server = createStdioServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error('Productive MCP server running on stdio');\n}\n\n// Start server when run directly\nconst isMainModule = import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('/productive-mcp') ||\n process.argv[1]?.endsWith('\\\\productive-mcp');\n\nif (isMainModule) {\n startStdioServer().catch((error) => {\n console.error('Fatal error:', error);\n process.exit(1);\n });\n}\n"],"names":[],"mappings":";;;;;AAyCO,SAAS,oBAA4B;AAC1C,QAAM,SAAS,IAAI;AAAA,IACjB;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IAAA;AAAA,IAEX;AAAA,MACE,cAAc;AAAA,QACZ,OAAO,CAAA;AAAA,QACP,SAAS,CAAA;AAAA,MAAC;AAAA,IACZ;AAAA,EACF;AAIF,SAAO,kBAAkB,wBAAwB,YAAY;AAC3D,WAAO,EAAE,OAAO,oBAAkB;AAAA,EACpC,CAAC;AAGD,SAAO,kBAAkB,0BAA0B,YAAY;AAC7D,WAAO,EAAE,SAAS,sBAAoB;AAAA,EACxC,CAAC;AAGD,SAAO,kBAAkB,wBAAwB,OAAO,YAAY;AAClE,WAAO,aAAa,QAAQ,OAAO,IAAI;AAAA,EACzC,CAAC;AAGD,SAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,UAAM,EAAE,MAAM,WAAW,KAAA,IAAS,QAAQ;AAE1C,QAAI;AACF,aAAO,MAAM,eAAe,MAAO,QAAoC,CAAA,CAAE;AAAA,IAC3E,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,OAAO,IAAI;AAAA,QACrD,SAAS;AAAA,MAAA;AAAA,IAEb;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAKA,eAAsB,mBAAkC;AACtD,QAAM,SAAS,kBAAA;AACf,QAAM,YAAY,IAAI,qBAAA;AACtB,QAAM,OAAO,QAAQ,SAAS;AAC9B,UAAQ,MAAM,wCAAwC;AACxD;AAGA,MAAM,eAAe,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAChE,QAAQ,KAAK,CAAC,GAAG,SAAS,iBAAiB,KAC3C,QAAQ,KAAK,CAAC,GAAG,SAAS,kBAAkB;AAE9C,IAAI,cAAc;AAChB,mBAAA,EAAmB,MAAM,CAAC,UAAU;AAClC,YAAQ,MAAM,gBAAgB,KAAK;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;"}
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Productive MCP Server - HTTP Transport
4
+ *
5
+ * This is the remote HTTP server mode for Claude Desktop custom connectors.
6
+ * Credentials are passed via Bearer token in the Authorization header.
7
+ *
8
+ * Token format: base64(organizationId:apiToken) or base64(organizationId:apiToken:userId)
9
+ *
10
+ * Generate your token:
11
+ * echo -n "YOUR_ORG_ID:YOUR_API_TOKEN:YOUR_USER_ID" | base64
12
+ *
13
+ * Usage:
14
+ * productive-mcp-server
15
+ * PORT=3000 productive-mcp-server
16
+ *
17
+ * Claude Desktop custom connector config:
18
+ * Name: Productive
19
+ * URL: https://productive.mcp.ikko.dev
20
+ * (No OAuth needed - uses Bearer token)
21
+ */
22
+ import { type Server } from 'node:http';
23
+ /**
24
+ * Start the HTTP server
25
+ */
26
+ export declare function startHttpServer(port?: number, host?: string): Promise<Server>;
27
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAOtD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,MAAqB,EAC3B,IAAI,GAAE,MAAqB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
package/dist/server.js ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import { createServer } from "node:http";
3
+ import { toNodeListener } from "h3";
4
+ import { createHttpApp } from "./http.js";
5
+ const DEFAULT_PORT = 3e3;
6
+ const DEFAULT_HOST = "0.0.0.0";
7
+ function startHttpServer(port = DEFAULT_PORT, host = DEFAULT_HOST) {
8
+ return new Promise((resolve) => {
9
+ const app = createHttpApp();
10
+ const server = createServer(toNodeListener(app));
11
+ server.listen(port, host, () => {
12
+ const displayHost = host === "0.0.0.0" ? "localhost" : host;
13
+ console.log(`Productive MCP server running at http://${displayHost}:${port}`);
14
+ console.log("");
15
+ console.log("Endpoints:");
16
+ console.log(` POST http://${displayHost}:${port}/mcp - MCP JSON-RPC endpoint`);
17
+ console.log(` GET http://${displayHost}:${port}/health - Health check`);
18
+ console.log("");
19
+ console.log("Authentication:");
20
+ console.log(" Pass Bearer token in Authorization header");
21
+ console.log(" Token format: base64(organizationId:apiToken:userId)");
22
+ console.log("");
23
+ console.log("Generate token:");
24
+ console.log(' echo -n "ORG_ID:API_TOKEN:USER_ID" | base64');
25
+ resolve(server);
26
+ });
27
+ });
28
+ }
29
+ const isMainModule = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("/productive-mcp-server") || process.argv[1]?.endsWith("\\productive-mcp-server");
30
+ if (isMainModule) {
31
+ const port = Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10);
32
+ const host = process.env.HOST || DEFAULT_HOST;
33
+ startHttpServer(port, host).catch((error) => {
34
+ console.error("Fatal error:", error);
35
+ process.exit(1);
36
+ });
37
+ }
38
+ export {
39
+ startHttpServer
40
+ };
41
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","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 { createServer, type Server } from 'node:http';\nimport { toNodeListener } from 'h3';\nimport { createHttpApp } from './http.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(toNodeListener(app));\n\n server.listen(port, host, () => {\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n console.log(`Productive MCP server 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('Authentication:');\n console.log(' Pass Bearer token in Authorization header');\n console.log(' Token format: base64(organizationId:apiToken:userId)');\n console.log('');\n console.log('Generate token:');\n console.log(' echo -n \"ORG_ID:API_TOKEN:USER_ID\" | base64');\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"],"names":[],"mappings":";;;;AA2BA,MAAM,eAAe;AACrB,MAAM,eAAe;AAKd,SAAS,gBACd,OAAe,cACf,OAAe,cACE;AACjB,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,MAAM,cAAA;AACZ,UAAM,SAAS,aAAa,eAAe,GAAG,CAAC;AAE/C,WAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,YAAM,cAAc,SAAS,YAAY,cAAc;AACvD,cAAQ,IAAI,2CAA2C,WAAW,IAAI,IAAI,EAAE;AAC5E,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,YAAY;AACxB,cAAQ,IAAI,iBAAiB,WAAW,IAAI,IAAI,8BAA8B;AAC9E,cAAQ,IAAI,iBAAiB,WAAW,IAAI,IAAI,wBAAwB;AACxE,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB;AAC7B,cAAQ,IAAI,6CAA6C;AACzD,cAAQ,IAAI,wDAAwD;AACpE,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB;AAC7B,cAAQ,IAAI,+CAA+C;AAC3D,cAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH,CAAC;AACH;AAGA,MAAM,eACJ,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAC7C,QAAQ,KAAK,CAAC,GAAG,SAAS,wBAAwB,KAClD,QAAQ,KAAK,CAAC,GAAG,SAAS,yBAAyB;AAErD,IAAI,cAAc;AAChB,QAAM,OAAO,OAAO,SAAS,QAAQ,IAAI,QAAQ,OAAO,YAAY,GAAG,EAAE;AACzE,QAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,kBAAgB,MAAM,IAAI,EAAE,MAAM,CAAC,UAAU;AAC3C,YAAQ,MAAM,gBAAgB,KAAK;AACnC,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Stdio transport handlers for Productive MCP Server
3
+ *
4
+ * This module contains the handler logic for the stdio transport.
5
+ * The actual server startup is in index.ts.
6
+ */
7
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
8
+ export type ToolResult = CallToolResult;
9
+ /**
10
+ * Get all available tools (including stdio-only configuration tools)
11
+ */
12
+ export declare function getAvailableTools(): {
13
+ inputSchema: {
14
+ [x: string]: unknown;
15
+ type: "object";
16
+ properties?: {
17
+ [x: string]: object;
18
+ } | undefined;
19
+ required?: string[] | undefined;
20
+ };
21
+ name: string;
22
+ description?: string | undefined;
23
+ outputSchema?: {
24
+ [x: string]: unknown;
25
+ type: "object";
26
+ properties?: {
27
+ [x: string]: object;
28
+ } | undefined;
29
+ required?: string[] | undefined;
30
+ } | undefined;
31
+ annotations?: {
32
+ title?: string | undefined;
33
+ readOnlyHint?: boolean | undefined;
34
+ destructiveHint?: boolean | undefined;
35
+ idempotentHint?: boolean | undefined;
36
+ openWorldHint?: boolean | undefined;
37
+ } | undefined;
38
+ execution?: {
39
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
40
+ } | undefined;
41
+ _meta?: {
42
+ [x: string]: unknown;
43
+ } | undefined;
44
+ icons?: {
45
+ src: string;
46
+ mimeType?: string | undefined;
47
+ sizes?: string[] | undefined;
48
+ theme?: "light" | "dark" | undefined;
49
+ }[] | undefined;
50
+ title?: string | undefined;
51
+ }[];
52
+ /**
53
+ * Get available prompts
54
+ */
55
+ export declare function getAvailablePrompts(): {
56
+ name: string;
57
+ description: string;
58
+ arguments: never[];
59
+ }[];
60
+ /**
61
+ * Handle the setup_productive prompt
62
+ */
63
+ export declare function handleSetupPrompt(): Promise<{
64
+ messages: Array<{
65
+ role: string;
66
+ content: {
67
+ type: string;
68
+ text: string;
69
+ };
70
+ }>;
71
+ }>;
72
+ /**
73
+ * Handle the productive_configure tool
74
+ */
75
+ export declare function handleConfigureTool(args: {
76
+ organizationId: string;
77
+ apiToken: string;
78
+ userId?: string;
79
+ }): Promise<ToolResult>;
80
+ /**
81
+ * Handle the productive_get_config tool
82
+ */
83
+ export declare function handleGetConfigTool(): Promise<ToolResult>;
84
+ /**
85
+ * Handle a tool call request
86
+ */
87
+ export declare function handleToolCall(name: string, args: Record<string, unknown>): Promise<ToolResult>;
88
+ /**
89
+ * Handle a prompt request
90
+ */
91
+ export declare function handlePrompt(name: string): Promise<{
92
+ messages: Array<{
93
+ role: string;
94
+ content: {
95
+ type: string;
96
+ text: string;
97
+ };
98
+ }>;
99
+ }>;
100
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAMzE,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAExC;;GAEG;AACH,wBAAgB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAEhC;AAED;;GAEG;AACH,wBAAgB,mBAAmB;;;;IAQlC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IACjD,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC5E,CAAC,CAiBD;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BtB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC,CAqB/D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC,CA8BrB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACxD,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC5E,CAAC,CAMD"}