taskr-mcp-client 1.0.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/README.md +49 -0
- package/bridge.js +111 -0
- package/index.js +116 -0
- package/package.json +40 -0
- package/test-stdin.js +20 -0
- package/test.js +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# taskr-mcp-client
|
|
2
|
+
|
|
3
|
+
stdio to HTTP bridge for Taskr MCP server. Connects Claude Code to the Taskr production MCP endpoint.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
No installation needed! Use with `npx`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx taskr-mcp-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage with Claude Code
|
|
14
|
+
|
|
15
|
+
Add to your `.mcp.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"taskr": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "taskr-mcp-client"],
|
|
23
|
+
"env": {
|
|
24
|
+
"MCP_API_URL": "https://taskr-six.vercel.app/api/mcp",
|
|
25
|
+
"MCP_PROJECT_ID": "your-project-id",
|
|
26
|
+
"MCP_USER_API_KEY": "your-api-key"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Environment Variables
|
|
34
|
+
|
|
35
|
+
- `MCP_API_URL` - Taskr MCP endpoint URL
|
|
36
|
+
- `MCP_PROJECT_ID` - Your Taskr project ID (format: `PR_*`)
|
|
37
|
+
- `MCP_USER_API_KEY` - Your Taskr API key
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
This bridge translates between:
|
|
42
|
+
- **stdin/stdout** (MCP protocol used by Claude Code)
|
|
43
|
+
- **HTTP POST** (JSON-RPC 2.0 endpoint used by Taskr server)
|
|
44
|
+
|
|
45
|
+
It allows Claude Code to communicate with Taskr's serverless MCP endpoint deployed on Vercel.
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT
|
package/bridge.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP HTTP Client Bridge using official MCP SDK
|
|
5
|
+
* Bridges stdio-based MCP clients to HTTP JSON-RPC endpoints
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
9
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
10
|
+
|
|
11
|
+
// Configuration from environment variables
|
|
12
|
+
const MCP_API_URL = process.env.MCP_API_URL;
|
|
13
|
+
const MCP_PROJECT_ID = process.env.MCP_PROJECT_ID;
|
|
14
|
+
const MCP_USER_API_KEY = process.env.MCP_USER_API_KEY;
|
|
15
|
+
|
|
16
|
+
if (!MCP_API_URL || !MCP_PROJECT_ID || !MCP_USER_API_KEY) {
|
|
17
|
+
console.error('Error: Missing required environment variables');
|
|
18
|
+
console.error('Required: MCP_API_URL, MCP_PROJECT_ID, MCP_USER_API_KEY');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Custom HTTP transport that wraps our JSON-RPC endpoint
|
|
23
|
+
class CustomHttpTransport {
|
|
24
|
+
constructor(url, headers) {
|
|
25
|
+
this.url = url;
|
|
26
|
+
this.headers = headers;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async start() {
|
|
30
|
+
// No persistent connection needed
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async send(message) {
|
|
34
|
+
const https = await import('https');
|
|
35
|
+
const http = await import('http');
|
|
36
|
+
|
|
37
|
+
const url = new URL(this.url);
|
|
38
|
+
const isHttps = url.protocol === 'https:';
|
|
39
|
+
const httpModule = isHttps ? https : http;
|
|
40
|
+
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const requestBody = JSON.stringify(message);
|
|
43
|
+
|
|
44
|
+
const options = {
|
|
45
|
+
hostname: url.hostname,
|
|
46
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
47
|
+
path: url.pathname,
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
'Content-Length': Buffer.byteLength(requestBody),
|
|
52
|
+
...this.headers
|
|
53
|
+
},
|
|
54
|
+
rejectUnauthorized: false
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const req = httpModule.request(options, (res) => {
|
|
58
|
+
let data = '';
|
|
59
|
+
|
|
60
|
+
res.on('data', (chunk) => {
|
|
61
|
+
data += chunk;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
res.on('end', () => {
|
|
65
|
+
try {
|
|
66
|
+
resolve(JSON.parse(data));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
reject(new Error('Failed to parse response'));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
req.on('error', reject);
|
|
74
|
+
req.write(requestBody);
|
|
75
|
+
req.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async close() {
|
|
80
|
+
// No persistent connection to close
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create and start the bridge
|
|
85
|
+
async function main() {
|
|
86
|
+
const transport = new CustomHttpTransport(MCP_API_URL, {
|
|
87
|
+
'x-project-id': MCP_PROJECT_ID,
|
|
88
|
+
'x-user-api-key': MCP_USER_API_KEY,
|
|
89
|
+
'MCP-Protocol-Version': '2025-06-18'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const client = new Client({
|
|
93
|
+
name: 'taskr-http-client',
|
|
94
|
+
version: '1.0.0'
|
|
95
|
+
}, {
|
|
96
|
+
capabilities: {}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Use stdio transport for communication with Claude Code
|
|
100
|
+
const stdioTransport = new StdioClientTransport();
|
|
101
|
+
|
|
102
|
+
await client.connect(transport);
|
|
103
|
+
|
|
104
|
+
// Keep process alive
|
|
105
|
+
process.stdin.resume();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main().catch((error) => {
|
|
109
|
+
console.error('Fatal error:', error);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP HTTP Client - stdio to HTTP bridge for MCP servers
|
|
5
|
+
* Bridges stdio-based MCP clients (like Claude Code) to HTTP-only JSON-RPC MCP servers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const http = require('http');
|
|
11
|
+
|
|
12
|
+
// Configuration from environment variables
|
|
13
|
+
const MCP_API_URL = process.env.MCP_API_URL;
|
|
14
|
+
const MCP_PROJECT_ID = process.env.MCP_PROJECT_ID;
|
|
15
|
+
const MCP_USER_API_KEY = process.env.MCP_USER_API_KEY;
|
|
16
|
+
|
|
17
|
+
if (!MCP_API_URL || !MCP_PROJECT_ID || !MCP_USER_API_KEY) {
|
|
18
|
+
process.stderr.write('Error: Missing required environment variables\n');
|
|
19
|
+
process.stderr.write('Required: MCP_API_URL, MCP_PROJECT_ID, MCP_USER_API_KEY\n');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Parse URL
|
|
24
|
+
const url = new URL(MCP_API_URL);
|
|
25
|
+
const isHttps = url.protocol === 'https:';
|
|
26
|
+
const httpModule = isHttps ? https : http;
|
|
27
|
+
|
|
28
|
+
// Read JSON-RPC requests from stdin, forward to HTTP endpoint, write responses to stdout
|
|
29
|
+
const rl = readline.createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
terminal: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
rl.on('line', async (line) => {
|
|
35
|
+
try {
|
|
36
|
+
// Parse JSON-RPC request from stdin
|
|
37
|
+
const jsonrpcRequest = JSON.parse(line);
|
|
38
|
+
|
|
39
|
+
// Forward to HTTP endpoint
|
|
40
|
+
const requestBody = JSON.stringify(jsonrpcRequest);
|
|
41
|
+
const options = {
|
|
42
|
+
hostname: url.hostname,
|
|
43
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
44
|
+
path: url.pathname,
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'Content-Length': Buffer.byteLength(requestBody),
|
|
49
|
+
'x-project-id': MCP_PROJECT_ID,
|
|
50
|
+
'x-user-api-key': MCP_USER_API_KEY,
|
|
51
|
+
'MCP-Protocol-Version': '2025-06-18'
|
|
52
|
+
},
|
|
53
|
+
// Disable SSL certificate validation (Windows SSL issues)
|
|
54
|
+
rejectUnauthorized: false
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const req = httpModule.request(options, (res) => {
|
|
58
|
+
let data = '';
|
|
59
|
+
|
|
60
|
+
res.on('data', (chunk) => {
|
|
61
|
+
data += chunk;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
res.on('end', () => {
|
|
65
|
+
try {
|
|
66
|
+
// Write JSON-RPC response to stdout (one line, newline terminated)
|
|
67
|
+
const output = data.trim() + '\n';
|
|
68
|
+
process.stdout.write(output, () => {
|
|
69
|
+
// Flush completed
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const errorResponse = {
|
|
73
|
+
jsonrpc: '2.0',
|
|
74
|
+
id: jsonrpcRequest.id,
|
|
75
|
+
error: {
|
|
76
|
+
code: -32603,
|
|
77
|
+
message: 'Internal error: Failed to parse server response'
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n', () => {
|
|
81
|
+
// Flush completed
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
req.on('error', (error) => {
|
|
88
|
+
const errorResponse = {
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
id: jsonrpcRequest.id,
|
|
91
|
+
error: {
|
|
92
|
+
code: -32603,
|
|
93
|
+
message: `HTTP request failed: ${error.message}`
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
req.write(requestBody);
|
|
100
|
+
req.end();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorResponse = {
|
|
103
|
+
jsonrpc: '2.0',
|
|
104
|
+
id: null,
|
|
105
|
+
error: {
|
|
106
|
+
code: -32700,
|
|
107
|
+
message: 'Parse error'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
rl.on('close', () => {
|
|
115
|
+
process.exit(0);
|
|
116
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "taskr-mcp-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "stdio to HTTP bridge for Taskr MCP server - connects Claude Code to HTTP-only JSON-RPC MCP endpoints",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"taskr-mcp-client": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"taskr",
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"json-rpc",
|
|
17
|
+
"bridge",
|
|
18
|
+
"stdio",
|
|
19
|
+
"http",
|
|
20
|
+
"ai",
|
|
21
|
+
"task-management"
|
|
22
|
+
],
|
|
23
|
+
"author": "Taskr Team",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/echo-of-machines/Taskr.git",
|
|
28
|
+
"directory": "mcp-http-client"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/echo-of-machines/Taskr#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/echo-of-machines/Taskr/issues"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.20.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/test-stdin.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
|
|
3
|
+
console.error('Starting test...');
|
|
4
|
+
|
|
5
|
+
const rl = readline.createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
terminal: false
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
rl.on('line', (line) => {
|
|
11
|
+
console.error(`Got line: ${line}`);
|
|
12
|
+
console.log(`ECHO: ${line}`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
rl.on('close', () => {
|
|
16
|
+
console.error('Stdin closed');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.error('Waiting for input...');
|
package/test.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
|
|
3
|
+
const url = new URL('https://taskr-six.vercel.app/api/mcp');
|
|
4
|
+
|
|
5
|
+
const data = JSON.stringify({
|
|
6
|
+
jsonrpc: '2.0',
|
|
7
|
+
id: 1,
|
|
8
|
+
method: 'tools/list',
|
|
9
|
+
params: {}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const options = {
|
|
13
|
+
hostname: url.hostname,
|
|
14
|
+
port: 443,
|
|
15
|
+
path: url.pathname,
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Content-Length': data.length,
|
|
20
|
+
'x-project-id': 'PRMGYS22KCZPJ0KRIQM100000000',
|
|
21
|
+
'x-user-api-key': '89cab946b8d6e51b38db993ba1441d617a06ff55e6f8c7174d0f6dd61d424bf2',
|
|
22
|
+
'MCP-Protocol-Version': '2025-06-18'
|
|
23
|
+
},
|
|
24
|
+
rejectUnauthorized: false
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
console.error('Making request to:', url.href);
|
|
28
|
+
console.error('Options:', JSON.stringify(options, null, 2));
|
|
29
|
+
|
|
30
|
+
const req = https.request(options, (res) => {
|
|
31
|
+
console.error('Status:', res.statusCode);
|
|
32
|
+
console.error('Headers:', JSON.stringify(res.headers, null, 2));
|
|
33
|
+
|
|
34
|
+
let responseData = '';
|
|
35
|
+
|
|
36
|
+
res.on('data', (chunk) => {
|
|
37
|
+
responseData += chunk;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
res.on('end', () => {
|
|
41
|
+
console.error('Response length:', responseData.length);
|
|
42
|
+
console.log(responseData);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
req.on('error', (error) => {
|
|
47
|
+
console.error('Request error:', error.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
req.write(data);
|
|
52
|
+
req.end();
|