driggsby 0.0.1 → 0.1.2

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,49 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { importJWK, jwtVerify, SignJWT, } from "jose";
3
+ export function generateLocalAuthToken() {
4
+ return randomUUID();
5
+ }
6
+ export function hashBrokerPayload(payload) {
7
+ return createHash("sha256").update(JSON.stringify(payload)).digest("base64url");
8
+ }
9
+ export async function signBrokerProof(claims, privateJwk) {
10
+ const signingKey = await importPrivateKey(privateJwk);
11
+ return await new SignJWT({
12
+ challenge: claims.challenge,
13
+ payloadSha256: claims.payloadSha256,
14
+ requestId: claims.requestId,
15
+ requestMethod: claims.requestMethod,
16
+ })
17
+ .setProtectedHeader({ alg: "ES256", typ: "JWT" })
18
+ .setAudience(claims.aud)
19
+ .setSubject(claims.sub)
20
+ .setIssuedAt()
21
+ .setJti(randomUUID())
22
+ .sign(signingKey);
23
+ }
24
+ export async function verifyBrokerProof(proof, publicJwk, expected) {
25
+ try {
26
+ const verificationKey = await importPublicKey(publicJwk);
27
+ const { payload } = await jwtVerify(proof, verificationKey, {
28
+ algorithms: ["ES256"],
29
+ audience: expected.aud,
30
+ subject: expected.sub,
31
+ });
32
+ return claimsMatch(payload, expected);
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ async function importPrivateKey(privateJwk) {
39
+ return await importJWK(privateJwk, "ES256");
40
+ }
41
+ async function importPublicKey(publicJwk) {
42
+ return await importJWK(publicJwk, "ES256");
43
+ }
44
+ function claimsMatch(payload, expected) {
45
+ return (payload["challenge"] === expected.challenge &&
46
+ payload["payloadSha256"] === expected.payloadSha256 &&
47
+ payload["requestId"] === expected.requestId &&
48
+ payload["requestMethod"] === expected.requestMethod);
49
+ }
@@ -0,0 +1,148 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import net from "node:net";
3
+ import { readBrokerMetadata, readBrokerLocalAuthToken } from "./installation.js";
4
+ import { decodeResponse, encodeRequest } from "./ipc.js";
5
+ import { hashBrokerPayload, verifyBrokerProof } from "./authentication.js";
6
+ export async function pingBroker(options) {
7
+ const response = await sendBrokerRequest(options, "ping");
8
+ if (response?.ok !== true) {
9
+ return null;
10
+ }
11
+ return response.result;
12
+ }
13
+ export async function getBrokerStatus(options) {
14
+ const response = await sendBrokerRequest(options, "get_status");
15
+ if (response?.ok !== true) {
16
+ return null;
17
+ }
18
+ return response.result.status;
19
+ }
20
+ export async function shutdownBroker(options) {
21
+ const response = await sendBrokerRequest(options, "shutdown");
22
+ return response?.ok === true && response.result.stopped;
23
+ }
24
+ export async function listBrokerTools(options) {
25
+ const response = await sendBrokerRequest(options, "list_tools");
26
+ if (response === null) {
27
+ return null;
28
+ }
29
+ if (!response.ok) {
30
+ throw new Error(response.error);
31
+ }
32
+ return response.result;
33
+ }
34
+ export async function callBrokerTool(options, toolName, args) {
35
+ const response = await sendBrokerRequest(options, "call_tool", {
36
+ toolName,
37
+ ...(args === undefined ? {} : { args }),
38
+ });
39
+ if (response === null) {
40
+ return null;
41
+ }
42
+ if (!response.ok) {
43
+ throw new Error(response.error);
44
+ }
45
+ return response.result;
46
+ }
47
+ async function sendBrokerRequest(options, method, extraFields) {
48
+ const metadata = await readBrokerMetadata(options.runtimePaths);
49
+ if (metadata === null) {
50
+ return null;
51
+ }
52
+ const authToken = await readBrokerLocalAuthToken(options.secretStore, metadata.brokerId);
53
+ if (authToken === null) {
54
+ return null;
55
+ }
56
+ const challenge = randomUUID();
57
+ const request = buildBrokerRequest(method, authToken, challenge, extraFields);
58
+ const response = await new Promise((resolve) => {
59
+ const socket = net.createConnection(options.runtimePaths.socketPath);
60
+ let buffer = "";
61
+ let settled = false;
62
+ const settle = (value) => {
63
+ if (settled) {
64
+ return;
65
+ }
66
+ settled = true;
67
+ resolve(value);
68
+ };
69
+ socket.setEncoding("utf8");
70
+ socket.setTimeout(1_500);
71
+ socket.once("connect", () => {
72
+ socket.write(encodeRequest(request));
73
+ });
74
+ socket.on("data", (chunk) => {
75
+ buffer += chunk;
76
+ const newlineIndex = buffer.indexOf("\n");
77
+ if (newlineIndex < 0) {
78
+ return;
79
+ }
80
+ const message = buffer.slice(0, newlineIndex);
81
+ try {
82
+ settle(decodeResponse(message));
83
+ }
84
+ catch {
85
+ settle(null);
86
+ }
87
+ finally {
88
+ socket.end();
89
+ }
90
+ });
91
+ socket.once("timeout", () => {
92
+ settle(null);
93
+ socket.destroy();
94
+ });
95
+ socket.once("error", () => {
96
+ settle(null);
97
+ });
98
+ socket.once("end", () => {
99
+ if (buffer.length === 0) {
100
+ settle(null);
101
+ }
102
+ });
103
+ });
104
+ if (response === null) {
105
+ return null;
106
+ }
107
+ const expectedClaims = buildExpectedClaims(request, response, metadata.brokerId);
108
+ const proofValid = await verifyBrokerProof(response.brokerProof, metadata.dpop.publicJwk, expectedClaims);
109
+ return proofValid ? response : null;
110
+ }
111
+ function buildBrokerRequest(method, authToken, challenge, extraFields) {
112
+ const baseRequest = {
113
+ authToken,
114
+ challenge,
115
+ id: randomUUID(),
116
+ };
117
+ if (method === "call_tool") {
118
+ const toolRequest = {
119
+ ...baseRequest,
120
+ method,
121
+ toolName: extraFields?.toolName ?? "",
122
+ };
123
+ if (extraFields?.args !== undefined) {
124
+ return {
125
+ ...toolRequest,
126
+ args: extraFields.args,
127
+ };
128
+ }
129
+ return toolRequest;
130
+ }
131
+ return {
132
+ ...baseRequest,
133
+ method,
134
+ };
135
+ }
136
+ function buildExpectedClaims(request, response, brokerId) {
137
+ const payload = response.ok
138
+ ? { ok: true, result: response.result }
139
+ : { ok: false, error: response.error };
140
+ return {
141
+ aud: "driggsby-local-shim",
142
+ challenge: request.challenge,
143
+ payloadSha256: hashBrokerPayload(payload),
144
+ requestId: request.id,
145
+ requestMethod: request.method,
146
+ sub: brokerId,
147
+ };
148
+ }
@@ -0,0 +1,64 @@
1
+ import { buildBrokerStatus, ensureBrokerInstallation, readBrokerDpopKeyPair, readBrokerLocalAuthToken, readBrokerPrivateJwk, } from "./installation.js";
2
+ import { BrokerRemoteSessionManager } from "./remote-session.js";
3
+ import { callRemoteTool, listRemoteTools } from "./remote-mcp.js";
4
+ import { KeyringSecretStore } from "./secret-store.js";
5
+ import { LocalBrokerServer } from "./server.js";
6
+ export async function runBrokerDaemon(runtimePaths) {
7
+ const secretStore = new KeyringSecretStore();
8
+ const metadata = await ensureBrokerInstallation(runtimePaths, secretStore);
9
+ const localAuthToken = await readRequiredLocalAuthToken(secretStore, metadata.brokerId);
10
+ const privateJwk = await readRequiredPrivateJwk(secretStore, metadata.brokerId);
11
+ const dpopKeyPair = await readRequiredDpopKeyPair(runtimePaths, secretStore, metadata.brokerId);
12
+ const remoteSessionManager = new BrokerRemoteSessionManager({
13
+ brokerId: metadata.brokerId,
14
+ runtimePaths,
15
+ secretStore,
16
+ });
17
+ const server = new LocalBrokerServer({
18
+ brokerId: metadata.brokerId,
19
+ callTool: async (toolName, args) => {
20
+ const session = await remoteSessionManager.ensureFreshSession();
21
+ return await callRemoteTool(session, dpopKeyPair, toolName, args);
22
+ },
23
+ localAuthToken,
24
+ listTools: async () => {
25
+ const session = await remoteSessionManager.ensureFreshSession();
26
+ return await listRemoteTools(session, dpopKeyPair);
27
+ },
28
+ privateJwk,
29
+ runtimePaths,
30
+ statusProvider: async () => await buildBrokerStatus(runtimePaths, secretStore, true),
31
+ });
32
+ await server.listen();
33
+ await new Promise((resolve, reject) => {
34
+ const shutdown = () => {
35
+ void server
36
+ .close()
37
+ .then(resolve)
38
+ .catch(reject);
39
+ };
40
+ process.once("SIGINT", shutdown);
41
+ process.once("SIGTERM", shutdown);
42
+ });
43
+ }
44
+ async function readRequiredLocalAuthToken(secretStore, brokerId) {
45
+ const localAuthToken = await readBrokerLocalAuthToken(secretStore, brokerId);
46
+ if (localAuthToken === null) {
47
+ throw new Error("The local broker auth state is incomplete. Run `npx -y driggsby login` again.");
48
+ }
49
+ return localAuthToken;
50
+ }
51
+ async function readRequiredPrivateJwk(secretStore, brokerId) {
52
+ const privateJwkRaw = await readBrokerPrivateJwk(secretStore, brokerId);
53
+ if (privateJwkRaw === null) {
54
+ throw new Error("The local broker signing key is missing. Run `npx -y driggsby login` again.");
55
+ }
56
+ return JSON.parse(privateJwkRaw);
57
+ }
58
+ async function readRequiredDpopKeyPair(runtimePaths, secretStore, brokerId) {
59
+ const dpopKeyPair = await readBrokerDpopKeyPair(runtimePaths, secretStore, brokerId);
60
+ if (dpopKeyPair === null) {
61
+ throw new Error("The local broker DPoP key is missing. Run `npx -y driggsby login` again.");
62
+ }
63
+ return dpopKeyPair;
64
+ }
@@ -0,0 +1,142 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import { generateDpopKeyMaterial } from "../auth/dpop.js";
4
+ import { readJsonFile, removeFileIfPresent, writeJsonFile } from "../lib/json-file.js";
5
+ import { generateLocalAuthToken } from "./authentication.js";
6
+ import { inspectRemoteSessionReadiness } from "./remote-session.js";
7
+ import { clearBrokerRemoteSession, readBrokerRemoteSession, } from "./session.js";
8
+ const LOCAL_AUTH_TOKEN_ACCOUNT_SUFFIX = "local-auth-token";
9
+ const PRIVATE_KEY_ACCOUNT_SUFFIX = "dpop-private-jwk";
10
+ export async function ensureBrokerInstallation(runtimePaths, secretStore) {
11
+ const readiness = await inspectBrokerReadiness(runtimePaths, secretStore);
12
+ if (readiness.installed && readiness.privateKeyPresent) {
13
+ const metadata = await readBrokerMetadata(runtimePaths);
14
+ if (metadata !== null) {
15
+ return metadata;
16
+ }
17
+ }
18
+ const brokerId = randomUUID();
19
+ const dpop = await generateDpopKeyMaterial();
20
+ const metadata = {
21
+ schemaVersion: 1,
22
+ brokerId,
23
+ createdAt: new Date().toISOString(),
24
+ dpop: {
25
+ algorithm: dpop.algorithm,
26
+ publicJwk: dpop.publicJwk,
27
+ thumbprint: dpop.thumbprint,
28
+ },
29
+ };
30
+ await secretStore.setSecret(localAuthTokenAccountName(brokerId), generateLocalAuthToken());
31
+ await secretStore.setSecret(privateKeyAccountName(brokerId), JSON.stringify(dpop.privateJwk));
32
+ await writeJsonFile(runtimePaths.metadataPath, metadata);
33
+ return metadata;
34
+ }
35
+ export async function inspectBrokerReadiness(runtimePaths, secretStore) {
36
+ const metadata = await readBrokerMetadata(runtimePaths);
37
+ if (metadata === null) {
38
+ return {
39
+ installed: false,
40
+ localAuthTokenPresent: false,
41
+ privateKeyPresent: false,
42
+ remoteSessionPresent: false,
43
+ };
44
+ }
45
+ const localAuthTokenPresent = (await secretStore.getSecret(localAuthTokenAccountName(metadata.brokerId))) !== null;
46
+ const privateKeyPresent = (await secretStore.getSecret(privateKeyAccountName(metadata.brokerId))) !== null;
47
+ const remoteSessionPresent = (await readBrokerRemoteSession(secretStore, metadata.brokerId)) !== null;
48
+ return {
49
+ installed: localAuthTokenPresent && privateKeyPresent,
50
+ brokerId: metadata.brokerId,
51
+ dpopThumbprint: metadata.dpop.thumbprint,
52
+ localAuthTokenPresent,
53
+ privateKeyPresent,
54
+ remoteSessionPresent,
55
+ };
56
+ }
57
+ export async function buildBrokerStatus(runtimePaths, secretStore, brokerRunning) {
58
+ const readiness = await inspectBrokerReadiness(runtimePaths, secretStore);
59
+ const remoteReadiness = readiness.brokerId === undefined
60
+ ? {
61
+ connected: false,
62
+ ready: false,
63
+ }
64
+ : await inspectRemoteSessionReadiness({
65
+ brokerId: readiness.brokerId,
66
+ runtimePaths,
67
+ secretStore,
68
+ });
69
+ const status = {
70
+ installed: readiness.installed && readiness.privateKeyPresent,
71
+ brokerRunning,
72
+ remoteMcpReady: remoteReadiness.ready,
73
+ socketPath: runtimePaths.socketPath,
74
+ };
75
+ if (readiness.brokerId !== undefined) {
76
+ Object.assign(status, { brokerId: readiness.brokerId });
77
+ }
78
+ if (readiness.dpopThumbprint !== undefined) {
79
+ Object.assign(status, { dpopThumbprint: readiness.dpopThumbprint });
80
+ }
81
+ if (remoteReadiness.session !== undefined) {
82
+ Object.assign(status, {
83
+ remoteSession: remoteReadiness.session,
84
+ });
85
+ }
86
+ return status;
87
+ }
88
+ export async function clearBrokerInstallation(runtimePaths, secretStore) {
89
+ const metadata = await readBrokerMetadata(runtimePaths);
90
+ if (metadata !== null) {
91
+ await clearBrokerRemoteSession(secretStore, metadata.brokerId);
92
+ await secretStore.deleteSecret(localAuthTokenAccountName(metadata.brokerId));
93
+ await secretStore.deleteSecret(privateKeyAccountName(metadata.brokerId));
94
+ }
95
+ await removeFileIfPresent(runtimePaths.metadataPath);
96
+ if (process.platform !== "win32") {
97
+ await removeFileIfPresent(runtimePaths.socketPath);
98
+ }
99
+ await removeEmptyDirectory(runtimePaths.configDir);
100
+ await removeEmptyDirectory(runtimePaths.stateDir);
101
+ }
102
+ export async function readBrokerMetadata(runtimePaths) {
103
+ return await readJsonFile(runtimePaths.metadataPath);
104
+ }
105
+ export async function readBrokerLocalAuthToken(secretStore, brokerId) {
106
+ return await secretStore.getSecret(localAuthTokenAccountName(brokerId));
107
+ }
108
+ export async function readBrokerPrivateJwk(secretStore, brokerId) {
109
+ return await secretStore.getSecret(privateKeyAccountName(brokerId));
110
+ }
111
+ export async function readBrokerDpopKeyPair(runtimePaths, secretStore, brokerId) {
112
+ const metadata = await readBrokerMetadata(runtimePaths);
113
+ if (metadata?.brokerId !== brokerId) {
114
+ return null;
115
+ }
116
+ const privateJwkRaw = await readBrokerPrivateJwk(secretStore, brokerId);
117
+ if (privateJwkRaw === null) {
118
+ return null;
119
+ }
120
+ return {
121
+ privateJwk: JSON.parse(privateJwkRaw),
122
+ publicJwk: metadata.dpop.publicJwk,
123
+ };
124
+ }
125
+ function localAuthTokenAccountName(brokerId) {
126
+ return `${brokerId}:${LOCAL_AUTH_TOKEN_ACCOUNT_SUFFIX}`;
127
+ }
128
+ function privateKeyAccountName(brokerId) {
129
+ return `${brokerId}:${PRIVATE_KEY_ACCOUNT_SUFFIX}`;
130
+ }
131
+ async function removeEmptyDirectory(directoryPath) {
132
+ try {
133
+ await fs.rmdir(directoryPath);
134
+ }
135
+ catch (error) {
136
+ if (!(error instanceof Error) ||
137
+ !("code" in error) ||
138
+ (error.code !== "ENOENT" && error.code !== "ENOTEMPTY")) {
139
+ throw error;
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,12 @@
1
+ export function encodeRequest(request) {
2
+ return `${JSON.stringify(request)}\n`;
3
+ }
4
+ export function decodeResponse(raw) {
5
+ return JSON.parse(raw);
6
+ }
7
+ export function decodeRequest(raw) {
8
+ return JSON.parse(raw);
9
+ }
10
+ export function encodeResponse(response) {
11
+ return `${JSON.stringify(response)}\n`;
12
+ }
@@ -0,0 +1,34 @@
1
+ import { spawn } from "node:child_process";
2
+ import { setTimeout as sleep } from "node:timers/promises";
3
+ import { getBrokerStatus, pingBroker } from "./client.js";
4
+ export async function ensureBrokerRunning(options) {
5
+ if ((await pingBroker({
6
+ runtimePaths: options.runtimePaths,
7
+ secretStore: options.secretStore,
8
+ })) !== null) {
9
+ return;
10
+ }
11
+ spawnBrokerDaemon(options.entrypointPath);
12
+ const started = await waitForBroker(options.runtimePaths, options.secretStore, 4_000);
13
+ if (!started) {
14
+ throw new Error("The local Driggsby broker did not start cleanly. Try `npx -y driggsby login` again.");
15
+ }
16
+ }
17
+ export async function waitForBroker(runtimePaths, secretStore, timeoutMs) {
18
+ const deadline = Date.now() + timeoutMs;
19
+ while (Date.now() < deadline) {
20
+ if ((await getBrokerStatus({ runtimePaths, secretStore })) !== null) {
21
+ return true;
22
+ }
23
+ await sleep(100);
24
+ }
25
+ return false;
26
+ }
27
+ function spawnBrokerDaemon(entrypointPath) {
28
+ const child = spawn(process.execPath, [...process.execArgv, entrypointPath, "broker-daemon"], {
29
+ detached: true,
30
+ stdio: "ignore",
31
+ windowsHide: true,
32
+ });
33
+ child.unref();
34
+ }
@@ -0,0 +1,84 @@
1
+ import { promises as fs } from "node:fs";
2
+ export async function acquireBrokerLock(lockPath) {
3
+ const initialHandle = await tryOpenExclusive(lockPath);
4
+ if (initialHandle !== null) {
5
+ return await createLease(lockPath, initialHandle);
6
+ }
7
+ if (!(await removeStaleLock(lockPath))) {
8
+ return null;
9
+ }
10
+ const recoveredHandle = await tryOpenExclusive(lockPath);
11
+ if (recoveredHandle === null) {
12
+ return null;
13
+ }
14
+ return await createLease(lockPath, recoveredHandle);
15
+ }
16
+ async function createLease(lockPath, handle) {
17
+ let released = false;
18
+ await handle.truncate(0);
19
+ await handle.writeFile(JSON.stringify({ pid: process.pid }));
20
+ return {
21
+ async release() {
22
+ if (released) {
23
+ return;
24
+ }
25
+ released = true;
26
+ await handle.close();
27
+ await removeFileIfPresent(lockPath);
28
+ },
29
+ };
30
+ }
31
+ async function tryOpenExclusive(lockPath) {
32
+ try {
33
+ return await fs.open(lockPath, "wx", 0o600);
34
+ }
35
+ catch (error) {
36
+ if (isErrnoException(error) && error.code === "EEXIST") {
37
+ return null;
38
+ }
39
+ throw error;
40
+ }
41
+ }
42
+ async function removeStaleLock(lockPath) {
43
+ const stalePid = await readLockPid(lockPath);
44
+ if (stalePid === null || processAlive(stalePid)) {
45
+ return false;
46
+ }
47
+ await removeFileIfPresent(lockPath);
48
+ return true;
49
+ }
50
+ async function readLockPid(lockPath) {
51
+ try {
52
+ const contents = await fs.readFile(lockPath, "utf8");
53
+ const parsed = JSON.parse(contents);
54
+ return typeof parsed.pid === "number" ? parsed.pid : null;
55
+ }
56
+ catch (error) {
57
+ if (isErrnoException(error) && error.code === "ENOENT") {
58
+ return null;
59
+ }
60
+ return null;
61
+ }
62
+ }
63
+ function processAlive(pid) {
64
+ try {
65
+ process.kill(pid, 0);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ async function removeFileIfPresent(filePath) {
73
+ try {
74
+ await fs.rm(filePath);
75
+ }
76
+ catch (error) {
77
+ if (!(isErrnoException(error) && error.code === "ENOENT")) {
78
+ throw error;
79
+ }
80
+ }
81
+ }
82
+ function isErrnoException(error) {
83
+ return error instanceof Error && "code" in error;
84
+ }
@@ -0,0 +1,121 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { CallToolResultSchema, ListToolsResultSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { createDpopProof } from "../auth/dpop.js";
5
+ import { assertBrokerRemoteUrl } from "../auth/url-security.js";
6
+ const BROKER_CLIENT_INFO = {
7
+ name: "driggsby-local-broker",
8
+ version: "0.1.0",
9
+ };
10
+ export async function listRemoteTools(session, dpopKeyPair) {
11
+ return await withRemoteClient(session, dpopKeyPair, async (client) => {
12
+ const result = await client.request({
13
+ method: "tools/list",
14
+ params: {},
15
+ }, ListToolsResultSchema);
16
+ return result.tools;
17
+ });
18
+ }
19
+ export async function callRemoteTool(session, dpopKeyPair, name, args) {
20
+ return await withRemoteClient(session, dpopKeyPair, async (client) => {
21
+ return await client.request({
22
+ method: "tools/call",
23
+ params: args === undefined
24
+ ? { name }
25
+ : {
26
+ arguments: args,
27
+ name,
28
+ },
29
+ }, CallToolResultSchema);
30
+ });
31
+ }
32
+ async function withRemoteClient(session, dpopKeyPair, action) {
33
+ assertBrokerRemoteUrl(session.resource, "The Driggsby MCP resource URL");
34
+ const transport = new StreamableHTTPClientTransport(new URL(session.resource), {
35
+ fetch: createDpopAuthenticatedFetch(session, dpopKeyPair),
36
+ });
37
+ const client = new Client(BROKER_CLIENT_INFO, {
38
+ capabilities: {},
39
+ });
40
+ try {
41
+ await client.connect(toTransport(transport));
42
+ return await action(client);
43
+ }
44
+ catch (error) {
45
+ throw mapRemoteMcpError(error);
46
+ }
47
+ finally {
48
+ await closeRemoteTransport(transport);
49
+ }
50
+ }
51
+ function toTransport(transport) {
52
+ return transport;
53
+ }
54
+ function mapRemoteMcpError(error) {
55
+ if (!(error instanceof Error)) {
56
+ return new Error("Driggsby could not complete that remote MCP request. Try again in a moment.");
57
+ }
58
+ const message = error.message.toLowerCase();
59
+ if (message.includes("tool") && message.includes("not found")) {
60
+ return new Error("That Driggsby tool is not available in this session anymore. Start a fresh client session and try again.");
61
+ }
62
+ if (message.includes("fetch failed") ||
63
+ message.includes("failed to open sse stream") ||
64
+ message.includes("error posting to endpoint") ||
65
+ message.includes("streamable http error") ||
66
+ message.includes("sse stream disconnected")) {
67
+ return new Error("Driggsby could not reach the remote MCP service right now. Try again in a moment.");
68
+ }
69
+ return error;
70
+ }
71
+ async function closeRemoteTransport(transport) {
72
+ try {
73
+ await transport.terminateSession();
74
+ }
75
+ catch {
76
+ // Best-effort cleanup only.
77
+ }
78
+ await transport.close();
79
+ }
80
+ function createDpopAuthenticatedFetch(session, dpopKeyPair) {
81
+ return async (input, init) => {
82
+ const httpMethod = resolveRequestMethod(input, init);
83
+ const headers = new Headers(input instanceof Request ? input.headers : undefined);
84
+ if (init?.headers !== undefined) {
85
+ new Headers(init.headers).forEach((value, key) => {
86
+ headers.set(key, value);
87
+ });
88
+ }
89
+ headers.set("Authorization", `${session.tokenType} ${session.accessToken}`);
90
+ headers.set("DPoP", await createDpopProof({
91
+ accessToken: session.accessToken,
92
+ httpMethod,
93
+ privateJwk: dpopKeyPair.privateJwk,
94
+ publicJwk: dpopKeyPair.publicJwk,
95
+ targetUrl: resolveRequestUrl(input),
96
+ }));
97
+ return await fetch(input, {
98
+ ...init,
99
+ headers,
100
+ method: httpMethod,
101
+ });
102
+ };
103
+ }
104
+ function resolveRequestMethod(input, init) {
105
+ if (init?.method !== undefined) {
106
+ return init.method;
107
+ }
108
+ if (input instanceof Request) {
109
+ return input.method;
110
+ }
111
+ return "GET";
112
+ }
113
+ function resolveRequestUrl(input) {
114
+ if (typeof input === "string") {
115
+ return input;
116
+ }
117
+ if (input instanceof URL) {
118
+ return input.toString();
119
+ }
120
+ return input.url;
121
+ }