Created
April 9, 2025 03:03
-
-
Save nwtgck/489811fdc79046cd422dfbd2c173a194 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(async () => { | |
const modelcontextprotocolSdk = "https://esm.sh/@modelcontextprotocol/[email protected]/dist/esm"; | |
const { McpServer } = await import(`${modelcontextprotocolSdk}/server/mcp.js`); | |
const { ReadBuffer, serializeMessage } = await import(`${modelcontextprotocolSdk}/shared/stdio.js`); | |
const { z } = await import("https://esm.sh/[email protected]/es2022/zod.mjs"); | |
const { Buffer } = await import("https://esm.sh/node/buffer.mjs"); | |
const sqliteDbPromise = (async () => { | |
const { default: sqlite3InitModule } = await import("https://esm.sh/@sqlite.org/[email protected]"); | |
const sqlite3 = await sqlite3InitModule({ print: console.log, printErr: console.error }); | |
return new sqlite3.oo1.DB(); | |
})(); | |
const server = new McpServer({ | |
name: "Browser Page Control", | |
version: "0.1.0" | |
}); | |
server.tool("browser_evaluate", | |
`Execute JavaScript in a browser page`, | |
{ | |
script: z.string().describe(`\ | |
The script is a JavaScript expression that will be evaluated in a page. | |
The result is wrapped in JSON.stringify() to convert the evaluated value to a JSON string. | |
A Promise is awaited using async/await syntax | |
The result of "(async () => { await new Promise(resolve => setTimeout(resolve, 1000)); return "hello" })()" is "hello" | |
No page refreshing or navigation is performed, as that would stop the MCP server. | |
`), | |
}, | |
async ({ script }) => ({ | |
content: [{ type: "text", text: JSON.stringify(await eval(script)) }] | |
}), | |
); | |
server.tool("sqlite_exec", | |
`SQLite execution`, | |
{ | |
query: z.string().describe(`\ | |
The result is wrapped in JSON.stringify() to convert the rows to a JSON string. | |
`), | |
}, | |
async ({ query }) => { | |
const db = await sqliteDbPromise; | |
const rows = []; | |
db.exec(query, { | |
callback: (row) => rows.push(row), | |
}); | |
return { | |
content: [{ type: "text", text: JSON.stringify(rows) }] | |
}; | |
}, | |
); | |
class PipingServerTransport { | |
sessionId; | |
onclose; | |
onerror; | |
onmessage; | |
pipingServer; | |
_csPath; | |
_scPath; | |
_readBuffer = new ReadBuffer(); | |
_started = false; | |
scReadable; | |
scWriter; | |
csReader; | |
abortController = new AbortController(); | |
constructor(pipingServer, csPath, scPath) { | |
this.sessionId = Math.random().toString(36); | |
this.pipingServer = pipingServer; | |
this._csPath = csPath; | |
this._scPath = scPath; | |
const transformStream = new TransformStream(); | |
this.scReadable = transformStream.readable; | |
this.scWriter = transformStream.writable.getWriter(); | |
} | |
async start() { | |
if (this._started) { | |
throw new Error("PipingServerTransport already started"); | |
} | |
this._started = true; | |
try { | |
const scResPromise = fetch(this.pipingServer + "/" + this._scPath, { | |
method: "POST", | |
body: this.scReadable, | |
duplex: "half", | |
signal: this.abortController.signal, | |
}); | |
const csRes = await fetch(this.pipingServer + "/" + this._csPath, { | |
method: "GET", | |
signal: this.abortController.signal, | |
}); | |
if (csRes.status !== 200) { | |
throw new Error(`GET status is not 200: ${csRes.status}`); | |
} | |
(async () => { | |
this.csReader = csRes.body.getReader(); | |
while (true) { | |
const { value, done } = await this.csReader.read(); | |
if (done) { | |
break; | |
} | |
this._readBuffer.append(Buffer.from(value)); | |
while (true) { | |
const message = this._readBuffer.readMessage(); | |
if (message === null) { | |
break; | |
} | |
console.debug("message from client", message); | |
this.onmessage?.(message); | |
} | |
} | |
const postRes = await scResPromise; | |
if (postRes.status !== 200) { | |
throw new Error(`POST status is not 200: ${postRes.status}`); | |
} | |
await postRes.text(); | |
this.onclose?.(); | |
})().catch(err => { | |
console.error(err); | |
this.onerror?.(err); | |
}); | |
} catch (err) { | |
this.onerror?.(err); | |
throw err; | |
} | |
} | |
async send(message) { | |
console.debug("message from server", message); | |
await this.scWriter.write(new TextEncoder().encode(serializeMessage(message))); | |
} | |
async close() { | |
this.abortController.abort(); | |
await this.scWriter.abort(); | |
await this.csReader.cancel(); | |
} | |
} | |
console.log("Starting MCP server..."); | |
const transport = new PipingServerTransport("https://ppng.io", <specify path (client to server)>, <specify path (server to client)>); | |
await server.connect(transport); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment