Penpot MCP: gotchas, template script, workflow documenté
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Client MCP - Streamable HTTP transport (MCP 2025-03-26)
|
||||
* La réponse initialize est en SSE, les suivantes aussi.
|
||||
*/
|
||||
import http from 'http';
|
||||
|
||||
const MCP_HOST = '192.168.1.150';
|
||||
const MCP_PORT = 9002;
|
||||
|
||||
let sessionId = null;
|
||||
let msgId = 1;
|
||||
|
||||
function postMCP(body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = JSON.stringify(body);
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
'Accept': 'application/json, text/event-stream',
|
||||
};
|
||||
if (sessionId) headers['mcp-session-id'] = sessionId;
|
||||
|
||||
const req = http.request({
|
||||
hostname: MCP_HOST, port: MCP_PORT, path: '/mcp', method: 'POST', headers
|
||||
}, res => {
|
||||
// Récupère session ID depuis header
|
||||
if (res.headers['mcp-session-id']) sessionId = res.headers['mcp-session-id'];
|
||||
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
const ct = res.headers['content-type'] || '';
|
||||
|
||||
if (ct.includes('text/event-stream')) {
|
||||
// Parser SSE
|
||||
let result = null;
|
||||
let error = null;
|
||||
for (const line of data.split('\n')) {
|
||||
if (line.startsWith('data:')) {
|
||||
const raw = line.slice(5).trim();
|
||||
if (!raw || raw === '[DONE]') continue;
|
||||
try {
|
||||
const j = JSON.parse(raw);
|
||||
if (j.result !== undefined) result = j.result;
|
||||
if (j.error) error = j.error;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
if (error) reject(new Error(JSON.stringify(error)));
|
||||
else resolve(result);
|
||||
} else {
|
||||
// JSON direct
|
||||
try {
|
||||
const j = JSON.parse(data);
|
||||
if (j.error) reject(new Error(JSON.stringify(j.error)));
|
||||
else resolve(j.result ?? j);
|
||||
} catch { resolve(data || null); }
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(payload);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export async function init() {
|
||||
await postMCP({
|
||||
jsonrpc: '2.0', id: msgId++, method: 'initialize',
|
||||
params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'nox', version: '1.0' } }
|
||||
});
|
||||
// Notification initialized
|
||||
await postMCP({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }).catch(() => {});
|
||||
console.log('✅ MCP Session:', sessionId);
|
||||
}
|
||||
|
||||
export async function toolRaw(name, args = {}) {
|
||||
return postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } });
|
||||
}
|
||||
|
||||
export async function tool(name, args = {}) {
|
||||
const result = await toolRaw(name, args);
|
||||
if (!result) return null;
|
||||
if (result.content && result.content[0]) {
|
||||
const text = result.content[0].text;
|
||||
try { return JSON.parse(text); } catch { return text; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export { sessionId };
|
||||
Reference in New Issue
Block a user