@veyanet/mcp 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.
@@ -0,0 +1,91 @@
1
+ # HTTP Transport — VEYA MCP
2
+
3
+ ## Protocol
4
+
5
+ `@veyanet/mcp` implements **MCP Streamable HTTP** using `@modelcontextprotocol/sdk`’s `StreamableHTTPServerTransport`.
6
+
7
+ Public clients (Claude, Cursor) should configure:
8
+
9
+ * Transport: Streamable HTTP (HTTP)
10
+ * URL: `https://mcp.veyanet.tech/mcp`
11
+
12
+ ## Endpoints
13
+
14
+ ### `GET /`
15
+
16
+ Minimal HTML landing page: paste URL, local POST hint, health link, honesty line (testnet 46630 · AES-256-GCM · not mainnet), pointer to stdio sibling path for operators.
17
+
18
+ ### `GET /health`
19
+
20
+ JSON honesty probe. Does not require MCP session. Safe to scrape from uptime monitors.
21
+
22
+ Example shape:
23
+
24
+ ```json
25
+ {
26
+ "status": "ok",
27
+ "service": "@veyanet/mcp",
28
+ "version": "1.0.0",
29
+ "publicMcpUrl": "https://mcp.veyanet.tech/mcp",
30
+ "chainId": 46630,
31
+ "contractAddress": "0x1a1Dc3c55550FCE9F70ef6cDEeF967c0b72a5d84",
32
+ "writesEnabled": false,
33
+ "sealed": "AES-256-GCM (not FHE)",
34
+ "settlement": "Robinhood Chain testnet 46630"
35
+ }
36
+ ```
37
+
38
+ ### `POST /mcp`
39
+
40
+ Stateless Streamable HTTP handler. Typical Accept header from clients:
41
+
42
+ ```http
43
+ Accept: application/json, text/event-stream
44
+ Content-Type: application/json
45
+ ```
46
+
47
+ Body is JSON-RPC MCP messages (initialize, tools/list, tools/call, …). Response may be JSON or SSE (`text/event-stream`) depending on client/transport negotiation.
48
+
49
+ Each request constructs an MCP server instance, connects a transport, handles the request, and closes on response end.
50
+
51
+ ### `POST /mcp/session`
52
+
53
+ Optional path for clients that use `MCP-Session-Id`. Initialize without a session id creates a session; subsequent calls reuse the transport map. Prefer `/mcp` for simple paste-URL clients unless your client requires sessions.
54
+
55
+ ## CORS
56
+
57
+ Configured in `createHttpApp`:
58
+
59
+ * No `Origin` → allowed (server-side connectors)
60
+ * `Origin` present → must be listed in `CORS_ORIGIN`
61
+ * Credentials enabled for allowlisted origins
62
+
63
+ ## Proxy requirements
64
+
65
+ When placing nginx (or similar) in front:
66
+
67
+ 1. `proxy_http_version 1.1`
68
+ 2. Forward `Authorization`
69
+ 3. Forward `X-Forwarded-Proto`
70
+ 4. `proxy_buffering off` for streaming responses
71
+ 5. Reasonable body size (MCP JSON is small; Express limit is `1mb`)
72
+
73
+ ## Timeouts
74
+
75
+ Tool handlers that call RPC or `api.veyanet.tech` use short fetch timeouts (e.g. 8s for API health). Upstream proxy idle timeouts should exceed typical tool latency (recommend ≥ 60s).
76
+
77
+ ## Local debugging
78
+
79
+ ```bash
80
+ npm start
81
+ curl -s http://127.0.0.1:8788/health
82
+ npm run smoke
83
+ ```
84
+
85
+ Smoke exercises initialize + tools/list + tool calls against an ephemeral listen port.
86
+
87
+ ## What this transport is not
88
+
89
+ * Not stdio MCP (see Anchor package)
90
+ * Not Server-Sent Events only (Streamable HTTP may use SSE framing but is not a raw EventSource API for operators to scrape manually)
91
+ * Not WebSocket MCP
@@ -0,0 +1,81 @@
1
+ # Verification Guide — VEYA MCP
2
+
3
+ How a stranger or auditor proves that MCP is talking to real Robinhood Chain settlement — without trusting marketing copy.
4
+
5
+ ## Goal
6
+
7
+ Prove three facts:
8
+
9
+ 1. The MCP endpoint is alive and honest (`/health`, `veya_describe`).
10
+ 2. RPC chain id is **46630**.
11
+ 3. A known (or newly written) commitment exists as a `Veya.sol` event and/or mapping.
12
+
13
+ ## Procedure A — Public read verification (no keys)
14
+
15
+ ### A1. Health
16
+
17
+ ```bash
18
+ curl -s https://mcp.veyanet.tech/health
19
+ ```
20
+
21
+ Require:
22
+ * `service` = `@veyanet/mcp`
23
+ * `chainId` = `46630`
24
+ * `sealed` contains `AES-256-GCM`
25
+ * `publicMcpUrl` = `https://mcp.veyanet.tech/mcp` (or your documented URL)
26
+
27
+ ### A2. Describe via MCP client
28
+
29
+ Connect Streamable HTTP to `https://mcp.veyanet.tech/mcp` and call `veya_describe`. Cross-check contract address against [NETWORK_PIN.md](./NETWORK_PIN.md).
30
+
31
+ ### A3. Ping
32
+
33
+ Call `veya_ping_chain`. Confirm chain id string matches `46630`.
34
+
35
+ ### A4. Verify transaction
36
+
37
+ Call `veya_verify_transaction` with:
38
+
39
+ ```text
40
+ 0xd68ab19671f0a3be63651cb6d6e24f5decf591da981708502827bca3689d31d8
41
+ ```
42
+
43
+ Open the explorer URL from the tool result. Confirm the transaction targets `Veya.sol` and the event digest matches the tool output.
44
+
45
+ ### A5. Optional independent SDK check
46
+
47
+ From `robinhood/sdk`:
48
+
49
+ ```bash
50
+ npm run example:verify
51
+ # or
52
+ npx tsx examples/verify-commitment.ts <txHash>
53
+ ```
54
+
55
+ This uses the same settlement pins and can `eth_call` `commitments(digest)`.
56
+
57
+ ## Procedure B — Operator write verification (keys required)
58
+
59
+ 1. Enable writes on a **non-public** or tightly controlled instance first.
60
+ 2. Call `veya_hash_blake3` to produce a 32-byte digest.
61
+ 3. Call `veya_store_commitment` with Bearer auth and a registered environment UUID.
62
+ 4. Call `veya_verify_transaction` on the returned `txHash`.
63
+ 5. Optionally re-check with SDK `verifyCommitmentOnChain`.
64
+
65
+ If Bearer is wrong, the tool must fail. If keys are unset, write tools must not be available as an open surface.
66
+
67
+ ## What verification does not prove
68
+
69
+ * That sealed-node AES ciphertext for some other workload is correct (different component).
70
+ * That product API guest JWT auth is correct (different component).
71
+ * That mainnet exists for VEYA (it is not claimed).
72
+
73
+ ## Evidence pack for diligence
74
+
75
+ | Artifact | Source |
76
+ |----------|--------|
77
+ | `/health` JSON | curl |
78
+ | `veya_describe` JSON | MCP client |
79
+ | Explorer tx link | tool `explorer` field |
80
+ | SDK verify output | `npm run example:verify` |
81
+ | Package version | `package.json` / health `version` |
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@veyanet/mcp",
3
+ "version": "1.0.0",
4
+ "description": "VEYA MCP - Streamable HTTP Model Context Protocol for Robinhood Chain (testnet 46630)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/veyanet/veya-mcp.git"
10
+ },
11
+ "homepage": "https://mcp.veyanet.tech/mcp",
12
+ "bugs": {
13
+ "url": "https://github.com/veyanet/veya-mcp/issues"
14
+ },
15
+ "bin": {
16
+ "veya-mcp": "./dist/cli.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "docs",
30
+ "assets",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "SECURITY.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "dev": "tsx watch src/cli.ts",
39
+ "start": "node dist/cli.js",
40
+ "lint": "tsc --noEmit",
41
+ "test": "tsx --test src/**/*.test.ts",
42
+ "smoke": "tsx scripts/smoke.ts"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.30.0",
46
+ "@veyanet/sdk": "^1.2.0",
47
+ "cors": "^2.8.5",
48
+ "express": "^4.21.2",
49
+ "zod": "^3.24.2"
50
+ },
51
+ "devDependencies": {
52
+ "@types/cors": "^2.8.17",
53
+ "@types/express": "^4.17.21",
54
+ "@types/node": "^22.15.29",
55
+ "tsup": "^8.5.0",
56
+ "tsx": "^4.19.4",
57
+ "typescript": "^5.9.3"
58
+ },
59
+ "engines": {
60
+ "node": ">=20"
61
+ },
62
+ "keywords": [
63
+ "veya",
64
+ "mcp",
65
+ "model-context-protocol",
66
+ "robinhood-chain",
67
+ "streamable-http"
68
+ ]
69
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Request } from "express";
2
+ import type { McpServiceConfig } from "./config.js";
3
+
4
+ export function extractBearer(req: Request): string | null {
5
+ const h = req.header("authorization") || req.header("Authorization");
6
+ if (!h) return null;
7
+ const m = /^Bearer\s+(.+)$/i.exec(h.trim());
8
+ return m?.[1]?.trim() || null;
9
+ }
10
+
11
+ /** Write tools require MCP_API_KEY match. Fail closed if key unset. */
12
+ export function assertWriteAuthorized(cfg: McpServiceConfig, bearer: string | null): void {
13
+ if (!cfg.mcpApiKey) {
14
+ throw new Error(
15
+ "Write tools disabled on this server (set MCP_API_KEY + VEYA_RELAYER_PRIVATE_KEY)",
16
+ );
17
+ }
18
+ if (!bearer || bearer !== cfg.mcpApiKey) {
19
+ throw new Error("Unauthorized: provide Authorization: Bearer <MCP_API_KEY>");
20
+ }
21
+ if (!cfg.relayerPrivateKey) {
22
+ throw new Error("Write tools disabled: VEYA_RELAYER_PRIVATE_KEY not configured");
23
+ }
24
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { startHttpServer } from "./http.js";
3
+ import { loadConfig } from "./config.js";
4
+
5
+ const cfg = loadConfig();
6
+ startHttpServer(cfg);
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { loadConfig, writesEnabled, MCP_SERVICE_NAME } from "./config.js";
4
+ import { createHttpApp } from "./http.js";
5
+
6
+ describe("veyanet mcp config", () => {
7
+ it("defaults to testnet 46630", () => {
8
+ delete process.env.ROBINHOOD_CHAIN_ID;
9
+ const cfg = loadConfig();
10
+ assert.equal(cfg.chainId, 46630);
11
+ assert.match(cfg.contractAddress, /^0x1a1D/i);
12
+ });
13
+
14
+ it("writes disabled without keys", () => {
15
+ delete process.env.MCP_API_KEY;
16
+ delete process.env.VEYA_RELAYER_PRIVATE_KEY;
17
+ delete process.env.VEYA_DEPLOYER_PRIVATE_KEY;
18
+ const cfg = loadConfig();
19
+ assert.equal(writesEnabled(cfg), false);
20
+ });
21
+ });
22
+
23
+ describe("veyanet mcp http", () => {
24
+ it("GET /health returns honesty fields", async () => {
25
+ const cfg = loadConfig();
26
+ cfg.port = 0;
27
+ const app = createHttpApp(cfg);
28
+ const server = app.listen(0);
29
+ try {
30
+ const addr = server.address();
31
+ assert.ok(addr && typeof addr === "object");
32
+ const res = await fetch(`http://127.0.0.1:${addr.port}/health`);
33
+ assert.equal(res.status, 200);
34
+ const body = (await res.json()) as Record<string, unknown>;
35
+ assert.equal(body.service, MCP_SERVICE_NAME);
36
+ assert.equal(body.chainId, 46630);
37
+ assert.match(String(body.sealed), /AES-256-GCM/);
38
+ } finally {
39
+ await new Promise<void>((resolve, reject) =>
40
+ server.close((err) => (err ? reject(err) : resolve())),
41
+ );
42
+ }
43
+ });
44
+ });
package/src/config.ts ADDED
@@ -0,0 +1,49 @@
1
+ export type McpServiceConfig = {
2
+ port: number;
3
+ host: string;
4
+ publicMcpUrl: string;
5
+ chainId: number;
6
+ rpcUrl: string;
7
+ explorerUrl: string;
8
+ contractAddress: string;
9
+ apiUrl: string;
10
+ mcpApiKey: string | null;
11
+ relayerPrivateKey: string | null;
12
+ corsOrigins: string[];
13
+ nodeEnv: string;
14
+ };
15
+
16
+ export const MCP_SERVICE_NAME = "@veyanet/mcp";
17
+ export const MCP_SERVICE_VERSION = "1.0.0";
18
+
19
+ export function loadConfig(): McpServiceConfig {
20
+ const mcpApiKey = process.env.MCP_API_KEY?.trim() || null;
21
+ const relayer =
22
+ process.env.VEYA_RELAYER_PRIVATE_KEY?.trim() ||
23
+ process.env.VEYA_DEPLOYER_PRIVATE_KEY?.trim() ||
24
+ null;
25
+
26
+ return {
27
+ port: Number(process.env.PORT || 8788),
28
+ host: process.env.HOST || "0.0.0.0",
29
+ publicMcpUrl: (process.env.PUBLIC_MCP_URL || "https://mcp.veyanet.tech/mcp").replace(/\/$/, ""),
30
+ chainId: Number(process.env.ROBINHOOD_CHAIN_ID || 46630),
31
+ rpcUrl: process.env.ROBINHOOD_RPC_URL || "https://rpc.testnet.chain.robinhood.com",
32
+ explorerUrl:
33
+ process.env.ROBINHOOD_EXPLORER_URL || "https://explorer.testnet.chain.robinhood.com",
34
+ contractAddress:
35
+ process.env.VEYA_CONTRACT_ADDRESS || "0x1a1Dc3c55550FCE9F70ef6cDEeF967c0b72a5d84",
36
+ apiUrl: (process.env.VEYA_API_URL || "https://api.veyanet.tech").replace(/\/$/, ""),
37
+ mcpApiKey,
38
+ relayerPrivateKey: relayer,
39
+ corsOrigins: (process.env.CORS_ORIGIN || "")
40
+ .split(",")
41
+ .map((s) => s.trim())
42
+ .filter(Boolean),
43
+ nodeEnv: process.env.NODE_ENV || "development",
44
+ };
45
+ }
46
+
47
+ export function writesEnabled(cfg: McpServiceConfig): boolean {
48
+ return Boolean(cfg.mcpApiKey && cfg.relayerPrivateKey);
49
+ }
package/src/http.ts ADDED
@@ -0,0 +1,143 @@
1
+ import express, { type Request, type Response } from "express";
2
+ import cors from "cors";
3
+ import { randomUUID } from "node:crypto";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
6
+ import {
7
+ loadConfig,
8
+ writesEnabled,
9
+ MCP_SERVICE_NAME,
10
+ MCP_SERVICE_VERSION,
11
+ type McpServiceConfig,
12
+ } from "./config.js";
13
+ import { extractBearer } from "./auth.js";
14
+ import { createMcpServer, requestAuth } from "./server.js";
15
+
16
+ export function createHttpApp(cfg: McpServiceConfig = loadConfig()) {
17
+ const app = express();
18
+ app.use(express.json({ limit: "1mb" }));
19
+
20
+ app.use(
21
+ cors({
22
+ origin(origin, cb) {
23
+ if (!origin) return cb(null, true);
24
+ if (cfg.corsOrigins.length === 0) return cb(null, false);
25
+ if (cfg.corsOrigins.includes(origin)) return cb(null, true);
26
+ return cb(null, false);
27
+ },
28
+ credentials: true,
29
+ }),
30
+ );
31
+
32
+ app.get("/health", (_req, res) => {
33
+ res.json({
34
+ status: "ok",
35
+ service: MCP_SERVICE_NAME,
36
+ version: MCP_SERVICE_VERSION,
37
+ publicMcpUrl: cfg.publicMcpUrl,
38
+ chainId: cfg.chainId,
39
+ contractAddress: cfg.contractAddress,
40
+ writesEnabled: writesEnabled(cfg),
41
+ sealed: "AES-256-GCM (not FHE)",
42
+ settlement: "Robinhood Chain testnet 46630",
43
+ });
44
+ });
45
+
46
+ // Stateless Streamable HTTP — paste URL into Claude / Cursor (POST /mcp)
47
+ app.post("/mcp", async (req: Request, res: Response) => {
48
+ const bearer = extractBearer(req);
49
+ await requestAuth.run({ bearer }, async () => {
50
+ try {
51
+ const server = createMcpServer(cfg);
52
+ const transport = new StreamableHTTPServerTransport({
53
+ sessionIdGenerator: undefined,
54
+ });
55
+ await server.connect(transport);
56
+ await transport.handleRequest(req, res, req.body);
57
+ res.on("close", () => {
58
+ void transport.close();
59
+ void server.close();
60
+ });
61
+ } catch (err) {
62
+ console.error("[veyanet/mcp] POST /mcp", err);
63
+ if (!res.headersSent) {
64
+ res.status(500).json({
65
+ jsonrpc: "2.0",
66
+ error: { code: -32603, message: "Internal server error" },
67
+ id: null,
68
+ });
69
+ }
70
+ }
71
+ });
72
+ });
73
+
74
+ // Optional stateful path for clients that send MCP-Session-Id
75
+ const sessions = new Map<string, StreamableHTTPServerTransport>();
76
+
77
+ app.post("/mcp/session", async (req: Request, res: Response) => {
78
+ const bearer = extractBearer(req);
79
+ await requestAuth.run({ bearer }, async () => {
80
+ try {
81
+ const sessionId = req.header("mcp-session-id") || undefined;
82
+ if (sessionId && sessions.has(sessionId)) {
83
+ await sessions.get(sessionId)!.handleRequest(req, res, req.body);
84
+ return;
85
+ }
86
+ if (!sessionId && isInitializeRequest(req.body)) {
87
+ const transport = new StreamableHTTPServerTransport({
88
+ sessionIdGenerator: () => randomUUID(),
89
+ onsessioninitialized: (id) => {
90
+ sessions.set(id, transport);
91
+ },
92
+ });
93
+ transport.onclose = () => {
94
+ if (transport.sessionId) sessions.delete(transport.sessionId);
95
+ };
96
+ const server = createMcpServer(cfg);
97
+ await server.connect(transport);
98
+ await transport.handleRequest(req, res, req.body);
99
+ return;
100
+ }
101
+ res.status(400).json({
102
+ jsonrpc: "2.0",
103
+ error: { code: -32000, message: "No valid MCP session" },
104
+ id: null,
105
+ });
106
+ } catch (err) {
107
+ console.error("[veyanet/mcp] POST /mcp/session", err);
108
+ if (!res.headersSent) {
109
+ res.status(500).json({
110
+ jsonrpc: "2.0",
111
+ error: { code: -32603, message: "Internal server error" },
112
+ id: null,
113
+ });
114
+ }
115
+ }
116
+ });
117
+ });
118
+
119
+ app.get("/", (_req, res) => {
120
+ res.type("html").send(`<!doctype html>
121
+ <html><head><meta charset="utf-8"/><title>VEYA MCP</title></head>
122
+ <body style="font-family:system-ui;max-width:40rem;margin:2rem auto;line-height:1.5">
123
+ <h1>VEYA MCP</h1>
124
+ <p>Paste this URL into Claude / Cursor (Streamable HTTP):</p>
125
+ <pre>${cfg.publicMcpUrl}</pre>
126
+ <p>Local: <code>POST http://127.0.0.1:${cfg.port}/mcp</code></p>
127
+ <p>Health: <a href="/health">/health</a></p>
128
+ <p>Settlement: Robinhood testnet <strong>46630</strong> · Sealed = AES-256-GCM (not FHE) · Not mainnet.</p>
129
+ <p>Local stdio MCP for operators: <code>veya-anchor/packages/mcp</code></p>
130
+ </body></html>`);
131
+ });
132
+
133
+ return app;
134
+ }
135
+
136
+ export function startHttpServer(cfg: McpServiceConfig = loadConfig()) {
137
+ const app = createHttpApp(cfg);
138
+ return app.listen(cfg.port, cfg.host, () => {
139
+ console.log(`[veyanet/mcp] listening on http://${cfg.host}:${cfg.port}`);
140
+ console.log(`[veyanet/mcp] MCP POST ${cfg.publicMcpUrl} (local /mcp)`);
141
+ console.log(`[veyanet/mcp] writesEnabled=${writesEnabled(cfg)}`);
142
+ });
143
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export {
2
+ loadConfig,
3
+ writesEnabled,
4
+ MCP_SERVICE_NAME,
5
+ MCP_SERVICE_VERSION,
6
+ type McpServiceConfig,
7
+ } from "./config.js";
8
+ export { createMcpServer } from "./server.js";
9
+ export { createHttpApp, startHttpServer } from "./http.js";
package/src/sdk.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { VeyaClient } from "@veyanet/sdk";
2
+ import type { McpServiceConfig } from "./config.js";
3
+
4
+ export function createReadClient(cfg: McpServiceConfig): VeyaClient {
5
+ return new VeyaClient({
6
+ rpcUrl: cfg.rpcUrl,
7
+ contractAddress: cfg.contractAddress,
8
+ chainId: cfg.chainId,
9
+ explorerUrl: cfg.explorerUrl,
10
+ });
11
+ }
12
+
13
+ export function createWriteClient(cfg: McpServiceConfig): VeyaClient {
14
+ if (!cfg.relayerPrivateKey) {
15
+ throw new Error("VEYA_RELAYER_PRIVATE_KEY required for write tools");
16
+ }
17
+ return new VeyaClient({
18
+ rpcUrl: cfg.rpcUrl,
19
+ contractAddress: cfg.contractAddress,
20
+ chainId: cfg.chainId,
21
+ explorerUrl: cfg.explorerUrl,
22
+ payerPrivateKey: cfg.relayerPrivateKey,
23
+ });
24
+ }
25
+
26
+ export function parseHexBytes(value: string, expectedLen?: number): Uint8Array {
27
+ const hex = value.trim().replace(/^0x/, "");
28
+ if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
29
+ throw new Error("Expected even-length hex string");
30
+ }
31
+ const buf = Buffer.from(hex, "hex");
32
+ if (expectedLen !== undefined && buf.length !== expectedLen) {
33
+ throw new Error(`Expected ${expectedLen} bytes, got ${buf.length}`);
34
+ }
35
+ return Uint8Array.from(buf);
36
+ }
package/src/server.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import {
4
+ MCP_SERVICE_NAME,
5
+ MCP_SERVICE_VERSION,
6
+ type McpServiceConfig,
7
+ } from "./config.js";
8
+ import { registerPublicTools } from "./tools/public.js";
9
+ import { registerWriteTools } from "./tools/write.js";
10
+
11
+ export const requestAuth = new AsyncLocalStorage<{ bearer: string | null }>();
12
+
13
+ export function createMcpServer(cfg: McpServiceConfig): McpServer {
14
+ const server = new McpServer({
15
+ name: MCP_SERVICE_NAME,
16
+ version: MCP_SERVICE_VERSION,
17
+ });
18
+
19
+ registerPublicTools(server, cfg);
20
+ registerWriteTools(server, cfg, () => requestAuth.getStore()?.bearer ?? null);
21
+
22
+ return server;
23
+ }