@patxin/mcp-server 0.1.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 +73 -0
- package/dist/api.d.ts +26 -0
- package/dist/api.js +76 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/ws.d.ts +14 -0
- package/dist/ws.js +79 -0
- package/dist/ws.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @patxin/mcp-server
|
|
2
|
+
|
|
3
|
+
MCP server for [patxin.com](https://patxin.com) — patch in a human for real-time cognitive micro-tasks.
|
|
4
|
+
|
|
5
|
+
When your AI agent needs human judgment — legal review, creative feedback, ethical decisions, domain expertise — it calls patxin and gets a matched human in under 60 seconds.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### Claude Desktop / Claude Code
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add patxin -- npx @patxin/mcp-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Cursor / other MCP clients
|
|
16
|
+
|
|
17
|
+
Add to your MCP configuration:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"patxin": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["@patxin/mcp-server"],
|
|
25
|
+
"env": {
|
|
26
|
+
"PATXIN_API_KEY": "pk_live_your_key_here"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Environment Variables
|
|
34
|
+
|
|
35
|
+
| Variable | Required | Description |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `PATXIN_API_KEY` | Yes | Your patxin API key (`pk_live_*` or `pk_test_*`) |
|
|
38
|
+
| `PATXIN_API_URL` | No | Custom API base URL (default: `https://api.patxin.com`) |
|
|
39
|
+
|
|
40
|
+
Test keys (`pk_test_*`) return simulated responses without real patxers or credit spend.
|
|
41
|
+
|
|
42
|
+
## Tools
|
|
43
|
+
|
|
44
|
+
| Tool | Description |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `patxin_check_limits` | Check remaining credits and request limits |
|
|
47
|
+
| `patxin_check_policy` | Check information policy and allowed skills |
|
|
48
|
+
| `patxin_estimate` | Get price estimate before committing |
|
|
49
|
+
| `patxin_request` | Request a human (blocks until completion) |
|
|
50
|
+
| `patxin_status` | Check status of an existing request |
|
|
51
|
+
| `patxin_rate` | Rate a completed interaction |
|
|
52
|
+
|
|
53
|
+
## Example
|
|
54
|
+
|
|
55
|
+
Once connected, your agent can use natural language:
|
|
56
|
+
|
|
57
|
+
> "I need a Spanish lawyer to review this NDA clause for compliance with GDPR Article 28."
|
|
58
|
+
|
|
59
|
+
The agent will call `patxin_estimate` to check pricing, then `patxin_request` to get a qualified human review.
|
|
60
|
+
|
|
61
|
+
## Real-time
|
|
62
|
+
|
|
63
|
+
The server uses WebSocket connections for instant task completion notifications, with automatic fallback to HTTP polling if WebSocket is unavailable.
|
|
64
|
+
|
|
65
|
+
## Links
|
|
66
|
+
|
|
67
|
+
- Website: https://patxin.com
|
|
68
|
+
- API docs: https://patxin.com/docs
|
|
69
|
+
- API health: https://api.patxin.com/v1/health
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare function checkLimits(): Promise<unknown>;
|
|
2
|
+
export declare function checkPolicy(): Promise<unknown>;
|
|
3
|
+
export declare function estimate(body: {
|
|
4
|
+
skill: string;
|
|
5
|
+
urgency: string;
|
|
6
|
+
duration: string;
|
|
7
|
+
}): Promise<unknown>;
|
|
8
|
+
export declare function createRequest(body: {
|
|
9
|
+
skill: string;
|
|
10
|
+
urgency: string;
|
|
11
|
+
duration: string;
|
|
12
|
+
context: string;
|
|
13
|
+
max_price: string;
|
|
14
|
+
information_policy?: string;
|
|
15
|
+
}): Promise<unknown>;
|
|
16
|
+
export declare function getRequestStatus(requestId: string): Promise<unknown>;
|
|
17
|
+
export declare function rateRequest(requestId: string, body: {
|
|
18
|
+
score: number;
|
|
19
|
+
comment?: string;
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
export declare function pollUntilComplete(requestId: string, intervalMs?: number, timeoutMs?: number): Promise<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Wait for a request to complete using WebSocket (instant push) with
|
|
24
|
+
* automatic fallback to HTTP polling if WS connection fails.
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitUntilComplete(requestId: string, timeoutMs?: number): Promise<unknown>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://api.patxin.com";
|
|
2
|
+
function getConfig() {
|
|
3
|
+
const apiKey = process.env.PATXIN_API_KEY;
|
|
4
|
+
if (!apiKey) {
|
|
5
|
+
throw new Error("PATXIN_API_KEY environment variable is required");
|
|
6
|
+
}
|
|
7
|
+
const baseUrl = process.env.PATXIN_API_URL || DEFAULT_BASE_URL;
|
|
8
|
+
return { apiKey, baseUrl };
|
|
9
|
+
}
|
|
10
|
+
async function request(method, path, body) {
|
|
11
|
+
const { apiKey, baseUrl } = getConfig();
|
|
12
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
13
|
+
method,
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `Bearer ${apiKey}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
},
|
|
18
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
19
|
+
});
|
|
20
|
+
const data = await res.json();
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const error = data.error;
|
|
23
|
+
throw new Error(error?.message || `API error: ${res.status}`);
|
|
24
|
+
}
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
export async function checkLimits() {
|
|
28
|
+
return request("GET", "/v1/account/limits");
|
|
29
|
+
}
|
|
30
|
+
export async function checkPolicy() {
|
|
31
|
+
return request("GET", "/v1/account/policy");
|
|
32
|
+
}
|
|
33
|
+
export async function estimate(body) {
|
|
34
|
+
return request("POST", "/v1/estimate", body);
|
|
35
|
+
}
|
|
36
|
+
export async function createRequest(body) {
|
|
37
|
+
return request("POST", "/v1/requests", body);
|
|
38
|
+
}
|
|
39
|
+
export async function getRequestStatus(requestId) {
|
|
40
|
+
return request("GET", `/v1/requests/${requestId}`);
|
|
41
|
+
}
|
|
42
|
+
export async function rateRequest(requestId, body) {
|
|
43
|
+
return request("POST", `/v1/requests/${requestId}/rate`, body);
|
|
44
|
+
}
|
|
45
|
+
export async function pollUntilComplete(requestId, intervalMs = 3000, timeoutMs = 600_000) {
|
|
46
|
+
const start = Date.now();
|
|
47
|
+
while (Date.now() - start < timeoutMs) {
|
|
48
|
+
const data = (await getRequestStatus(requestId));
|
|
49
|
+
if (data.status === "completed" ||
|
|
50
|
+
data.status === "expired" ||
|
|
51
|
+
data.status === "cancelled" ||
|
|
52
|
+
data.status === "disputed") {
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Request ${requestId} timed out after ${timeoutMs / 1000}s`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wait for a request to complete using WebSocket (instant push) with
|
|
61
|
+
* automatic fallback to HTTP polling if WS connection fails.
|
|
62
|
+
*/
|
|
63
|
+
export async function waitUntilComplete(requestId, timeoutMs = 600_000) {
|
|
64
|
+
const { apiKey, baseUrl } = getConfig();
|
|
65
|
+
try {
|
|
66
|
+
const { waitForCompletion } = await import("./ws.js");
|
|
67
|
+
// WS resolves with the event; we still fetch the full status via HTTP
|
|
68
|
+
await waitForCompletion(requestId, apiKey, baseUrl, timeoutMs);
|
|
69
|
+
return await getRequestStatus(requestId);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// WS failed (connection refused, timeout, etc.) — fall back to polling
|
|
73
|
+
return pollUntilComplete(requestId, 3000, timeoutMs);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { checkLimits, checkPolicy, estimate, createRequest, getRequestStatus, waitUntilComplete, rateRequest, } from "./api.js";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "patxin",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
});
|
|
10
|
+
// --- Tools ---
|
|
11
|
+
server.tool("patxin_check_limits", "Check your remaining credits, daily request limits, and concurrent request capacity. Call this before making a request to ensure you have budget.", {}, async () => {
|
|
12
|
+
try {
|
|
13
|
+
const data = await checkLimits();
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
server.tool("patxin_check_policy", "Check your information policy: what data you can share, which skills are allowed, and whether NDAs are required.", {}, async () => {
|
|
21
|
+
try {
|
|
22
|
+
const data = await checkPolicy();
|
|
23
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
server.tool("patxin_estimate", "Get a price estimate and availability for a human micro-task before committing. Returns estimated price, available patxers, and match time.", {
|
|
30
|
+
skill: z.string().describe("Required skill (e.g. 'legal_review_spain', 'code_review', 'translation_en_es')"),
|
|
31
|
+
urgency: z.enum(["1min", "5min", "15min", "30min"]).describe("How quickly you need a match. Higher urgency costs more."),
|
|
32
|
+
duration: z.string().describe("Expected task duration (e.g. '5min', '15min', '1h')"),
|
|
33
|
+
}, async ({ skill, urgency, duration }) => {
|
|
34
|
+
try {
|
|
35
|
+
const data = await estimate({ skill, urgency, duration });
|
|
36
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
server.tool("patxin_request", "Request a qualified human to complete a cognitive micro-task. This tool blocks until the human completes the task (or it expires/is cancelled). Use patxin_estimate first to check pricing. The context field should contain all information the human needs to complete the task.", {
|
|
43
|
+
skill: z.string().describe("Required skill (e.g. 'legal_review_spain', 'code_review')"),
|
|
44
|
+
urgency: z.enum(["1min", "5min", "15min", "30min"]).describe("How quickly you need a match"),
|
|
45
|
+
duration: z.string().describe("Expected task duration (e.g. '15min')"),
|
|
46
|
+
context: z.string().describe("Full context and instructions for the human. Be specific about what you need."),
|
|
47
|
+
max_price: z.string().describe("Maximum price you're willing to pay (e.g. '8EUR', '15USD')"),
|
|
48
|
+
information_policy: z.enum(["open", "redacted", "sandboxed"]).optional().describe("How to handle sensitive data. Defaults to 'redacted'."),
|
|
49
|
+
}, async ({ skill, urgency, duration, context, max_price, information_policy }) => {
|
|
50
|
+
try {
|
|
51
|
+
const created = (await createRequest({
|
|
52
|
+
skill,
|
|
53
|
+
urgency,
|
|
54
|
+
duration,
|
|
55
|
+
context,
|
|
56
|
+
max_price,
|
|
57
|
+
information_policy,
|
|
58
|
+
}));
|
|
59
|
+
const result = await waitUntilComplete(created.request_id);
|
|
60
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.tool("patxin_status", "Check the status of an existing patxin request by its ID. Use this if you need to check on a request without waiting for completion.", {
|
|
67
|
+
request_id: z.string().describe("The request ID (e.g. 'req_abc123')"),
|
|
68
|
+
}, async ({ request_id }) => {
|
|
69
|
+
try {
|
|
70
|
+
const data = await getRequestStatus(request_id);
|
|
71
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
server.tool("patxin_rate", "Rate a completed patxin request. Always rate interactions — it improves matching quality for future requests.", {
|
|
78
|
+
request_id: z.string().describe("The request ID to rate"),
|
|
79
|
+
score: z.number().int().min(1).max(5).describe("Rating from 1 (poor) to 5 (excellent)"),
|
|
80
|
+
comment: z.string().optional().describe("Optional feedback comment"),
|
|
81
|
+
}, async ({ request_id, score, comment }) => {
|
|
82
|
+
try {
|
|
83
|
+
const data = await rateRequest(request_id, { score, comment });
|
|
84
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// --- Start ---
|
|
91
|
+
const transport = new StdioServerTransport();
|
|
92
|
+
await server.connect(transport);
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/ws.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type RequestEvent = {
|
|
2
|
+
type: string;
|
|
3
|
+
request_id?: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
data?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Open a WebSocket to the patxin API and wait for a terminal status event
|
|
9
|
+
* for the given request ID.
|
|
10
|
+
*
|
|
11
|
+
* Resolves with the event data on terminal status, rejects on timeout or error.
|
|
12
|
+
*/
|
|
13
|
+
export declare function waitForCompletion(requestId: string, apiKey: string, baseUrl: string, timeoutMs?: number): Promise<RequestEvent>;
|
|
14
|
+
export {};
|
package/dist/ws.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
const TERMINAL_STATUSES = new Set(["completed", "expired", "cancelled", "disputed"]);
|
|
3
|
+
const PING_INTERVAL_MS = 30_000;
|
|
4
|
+
/**
|
|
5
|
+
* Open a WebSocket to the patxin API and wait for a terminal status event
|
|
6
|
+
* for the given request ID.
|
|
7
|
+
*
|
|
8
|
+
* Resolves with the event data on terminal status, rejects on timeout or error.
|
|
9
|
+
*/
|
|
10
|
+
export function waitForCompletion(requestId, apiKey, baseUrl, timeoutMs = 600_000) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const wsUrl = `${baseUrl.replace(/^http/, "ws")}/v1/ws?token=${encodeURIComponent(apiKey)}`;
|
|
13
|
+
let ws;
|
|
14
|
+
let pingTimer;
|
|
15
|
+
let timeoutTimer;
|
|
16
|
+
let settled = false;
|
|
17
|
+
function cleanup() {
|
|
18
|
+
settled = true;
|
|
19
|
+
clearInterval(pingTimer);
|
|
20
|
+
clearTimeout(timeoutTimer);
|
|
21
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
22
|
+
ws.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
ws = new WebSocket(wsUrl);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
reject(new Error(`WebSocket connection failed: ${err.message}`));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
timeoutTimer = setTimeout(() => {
|
|
33
|
+
if (!settled) {
|
|
34
|
+
cleanup();
|
|
35
|
+
reject(new Error(`WebSocket wait timed out after ${timeoutMs / 1000}s for ${requestId}`));
|
|
36
|
+
}
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
ws.on("open", () => {
|
|
39
|
+
// Keepalive pings
|
|
40
|
+
pingTimer = setInterval(() => {
|
|
41
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
42
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
43
|
+
}
|
|
44
|
+
}, PING_INTERVAL_MS);
|
|
45
|
+
});
|
|
46
|
+
ws.on("message", (raw) => {
|
|
47
|
+
if (settled)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
const event = JSON.parse(raw.toString());
|
|
51
|
+
// Only care about events for our request
|
|
52
|
+
if (event.request_id !== requestId)
|
|
53
|
+
return;
|
|
54
|
+
// Extract status from event type (e.g. "request.completed" → "completed")
|
|
55
|
+
const status = event.type.replace("request.", "");
|
|
56
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
57
|
+
cleanup();
|
|
58
|
+
resolve(event);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Ignore malformed messages
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
ws.on("error", (err) => {
|
|
66
|
+
if (!settled) {
|
|
67
|
+
cleanup();
|
|
68
|
+
reject(new Error(`WebSocket error: ${err.message}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
ws.on("close", () => {
|
|
72
|
+
if (!settled) {
|
|
73
|
+
cleanup();
|
|
74
|
+
reject(new Error("WebSocket connection closed unexpectedly"));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ws.js.map
|
package/dist/ws.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@patxin/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for patxin.com — patch in a human for real-time cognitive micro-tasks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"patxin-mcp-server": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.26",
|
|
22
|
+
"ws": "^8.19.0",
|
|
23
|
+
"zod": "^3.24"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22",
|
|
27
|
+
"@types/ws": "^8.18.1",
|
|
28
|
+
"tsx": "^4",
|
|
29
|
+
"typescript": "^5.7"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"mcp-server",
|
|
34
|
+
"patxin",
|
|
35
|
+
"human-in-the-loop",
|
|
36
|
+
"ai-agent",
|
|
37
|
+
"cognitive-tasks",
|
|
38
|
+
"model-context-protocol"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/agraciag/patxin.git",
|
|
44
|
+
"directory": "packages/mcp-server"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://patxin.com",
|
|
47
|
+
"author": "Alejandro Gracia García <alex@patxin.com>",
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
}
|
|
51
|
+
}
|