Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
191
deps/db-sync/worker/entry.mjs
vendored
Normal file
191
deps/db-sync/worker/entry.mjs
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
||||
import { openApiMcpServer } from "@cloudflare/codemode/mcp";
|
||||
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
||||
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
import { chatGptToolDescriptors } from "./chatgpt_app.mjs";
|
||||
import { uploadChatGptAsset } from "./chatgpt_asset_upload.mjs";
|
||||
import {
|
||||
ASSET_IMAGE_RESOURCE_URI,
|
||||
assertDisplayableImageMetadata,
|
||||
assetImageResource,
|
||||
assetImageResult,
|
||||
} from "./asset_image.mjs";
|
||||
import { apiDocsResponse } from "./api_docs.mjs";
|
||||
import { logseqIconResponse, setLogseqMcpServerInfoIcons } from "./logseq_icon.mjs";
|
||||
import { ensureMcpAcceptHeader } from "./mcp_request.mjs";
|
||||
import { openAiAppsChallengeResponse } from "./openai_app_challenge.mjs";
|
||||
import { semanticRequestBody, semanticRequestUrl } from "./semantic_request.mjs";
|
||||
import apiDocsHtml from "./dist/api-docs.generated.mjs";
|
||||
import worker, { SyncDO } from "./dist/worker/main.js";
|
||||
|
||||
function protectedResourceMetadata(request, env) {
|
||||
const url = new URL(request.url);
|
||||
return Response.json({
|
||||
resource: `${url.origin}/mcp`,
|
||||
authorization_servers: [env.COGNITO_ISSUER],
|
||||
scopes_supported: ["logseq/read", "logseq/write"],
|
||||
bearer_methods_supported: ["header"],
|
||||
});
|
||||
}
|
||||
|
||||
async function semanticSpec(request, env, ctx) {
|
||||
const url = new URL("/openapi.json", request.url);
|
||||
const response = await worker.fetch(new Request(url), env, ctx);
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenAPI request failed: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function handleMcp(request, env, ctx) {
|
||||
const authorization = request.headers.get("authorization");
|
||||
const server = openApiMcpServer({
|
||||
spec: await semanticSpec(request, env, ctx),
|
||||
executor: new DynamicWorkerExecutor({ loader: env.LOADER }),
|
||||
name: "logseq",
|
||||
version: "1.0.0",
|
||||
request: async (options) => {
|
||||
if (!options.path.startsWith("/api/v1/")) {
|
||||
throw new Error("Only Logseq server API paths are allowed");
|
||||
}
|
||||
const url = semanticRequestUrl(options, request.url);
|
||||
const headers = {};
|
||||
if (authorization) headers.authorization = authorization;
|
||||
if (options.contentType) headers["content-type"] = options.contentType;
|
||||
else if (options.body !== undefined) headers["content-type"] = "application/json";
|
||||
|
||||
const response = await worker.fetch(
|
||||
new Request(url, {
|
||||
method: options.method,
|
||||
headers,
|
||||
body: semanticRequestBody(options, url),
|
||||
}),
|
||||
env,
|
||||
ctx,
|
||||
);
|
||||
if (response.status === 204) return null;
|
||||
const result = (response.headers.get("content-type") ?? "").includes("application/json")
|
||||
? await response.json()
|
||||
: await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`Logseq API request failed: ${response.status} ${JSON.stringify(result)}`);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
setLogseqMcpServerInfoIcons(server, request);
|
||||
|
||||
server.registerResource("asset-image", ASSET_IMAGE_RESOURCE_URI, {
|
||||
title: "Logseq image asset",
|
||||
description: "Renders a Logseq image asset returned by the image asset tool.",
|
||||
mimeType: "text/html;profile=mcp-app",
|
||||
}, assetImageResource);
|
||||
|
||||
server.registerTool("upload_asset", {
|
||||
title: "Upload Logseq asset",
|
||||
description: "Upload an original ChatGPT attachment to a Logseq DB graph.",
|
||||
inputSchema: {
|
||||
graphId: z.string(),
|
||||
file: z.object({
|
||||
download_url: z.string(),
|
||||
file_id: z.string(),
|
||||
mime_type: z.string().optional(),
|
||||
file_name: z.string().optional(),
|
||||
}),
|
||||
pageId: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
},
|
||||
outputSchema: {
|
||||
uuid: z.string(),
|
||||
title: z.string(),
|
||||
type: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
checksum: z.string(),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
openWorldHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: false,
|
||||
},
|
||||
_meta: {
|
||||
"openai/fileParams": ["file"],
|
||||
},
|
||||
}, (input) => uploadChatGptAsset(input, {
|
||||
origin: new URL(request.url).origin,
|
||||
authorization,
|
||||
env,
|
||||
ctx,
|
||||
workerFetch: worker.fetch.bind(worker),
|
||||
}).then((result) => ({
|
||||
structuredContent: result,
|
||||
content: [{ type: "text", text: `Uploaded Logseq asset ${result.title} (${result.uuid}).` }],
|
||||
})));
|
||||
|
||||
server.registerTool("get_asset_image", {
|
||||
title: "Display Logseq image asset",
|
||||
description: "Return a Logseq image asset as MCP image content for direct display.",
|
||||
inputSchema: {
|
||||
graphId: z.string(),
|
||||
assetBlockId: z.string(),
|
||||
},
|
||||
outputSchema: {
|
||||
uuid: z.string(),
|
||||
title: z.string(),
|
||||
mimeType: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
openWorldHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
},
|
||||
_meta: {
|
||||
ui: { resourceUri: ASSET_IMAGE_RESOURCE_URI },
|
||||
"openai/outputTemplate": ASSET_IMAGE_RESOURCE_URI,
|
||||
},
|
||||
}, async ({ graphId, assetBlockId }) => {
|
||||
const metadataUrl = new URL(
|
||||
`/api/v1/graphs/${encodeURIComponent(graphId)}/assets/${encodeURIComponent(assetBlockId)}`,
|
||||
request.url,
|
||||
);
|
||||
const metadataResponse = await worker.fetch(new Request(metadataUrl, {
|
||||
headers: authorization ? { authorization } : {},
|
||||
}), env, ctx);
|
||||
const metadata = await metadataResponse.json();
|
||||
if (!metadataResponse.ok) {
|
||||
throw new Error(`Logseq API request failed: ${metadataResponse.status} ${JSON.stringify(metadata)}`);
|
||||
}
|
||||
assertDisplayableImageMetadata(metadata);
|
||||
const imageResponse = await worker.fetch(new Request(metadata.url), env, ctx);
|
||||
return assetImageResult(imageResponse, metadata);
|
||||
});
|
||||
|
||||
const transport = new WebStandardStreamableHTTPServerTransport();
|
||||
await server.connect(transport);
|
||||
server.server.setRequestHandler(ListToolsRequestSchema, () => ({
|
||||
tools: chatGptToolDescriptors(),
|
||||
}));
|
||||
return transport.handleRequest(request);
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
const path = new URL(request.url).pathname;
|
||||
const challengeResponse = openAiAppsChallengeResponse(request, env);
|
||||
if (challengeResponse) return challengeResponse;
|
||||
const docsResponse = apiDocsResponse(path, apiDocsHtml);
|
||||
if (docsResponse) return docsResponse;
|
||||
if (path === "/.well-known/oauth-protected-resource" ||
|
||||
path === "/.well-known/oauth-protected-resource/mcp") {
|
||||
return protectedResourceMetadata(request, env);
|
||||
}
|
||||
if (path === "/mcp/icon.png") return logseqIconResponse();
|
||||
if (path === "/mcp") return handleMcp(ensureMcpAcceptHeader(request), env, ctx);
|
||||
return worker.fetch(request, env, ctx);
|
||||
},
|
||||
};
|
||||
|
||||
export { SyncDO };
|
||||
Reference in New Issue
Block a user