mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-12 01:00:54 +00:00
* feat: add optional Streamable HTTP for External MCP Serve the existing catalog tools and approval path on a loopback HTTP endpoint so clients that cannot spawn the stdio launcher, such as Codex in WSL, can connect with a bearer token. The listener stays off until the user enables it. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: close HTTP sockets before stop and export the Codex bearer Destroy tracked connections before awaiting server.close so disable does not hang on a half-open request. Prefix the Codex snippet with export so the child process receives the token. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: advertise a real client host and return 413 intact Wildcard binds no longer copy 127.0.0.1 into client configs; the URL stays empty until a reachable client address is set. Oversized POSTs receive a JSON-RPC 413 before the socket is closed. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: recheck the bearer token after the HTTP body arrives A half-open POST already passed auth before readBody. Recheck the live token before dispatch so a rotation during that wait cannot still run a tool. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: expire idle Streamable HTTP sessions JSON responses do not keep a socket open, so a client that never sends DELETE used to pin an MCP session until the listener stopped. Sweep sessions that have been idle for 15 minutes, and skip any session with a request still in flight. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: return 404 when an HTTP session id is unknown GET and DELETE with a nonempty session id that is no longer active now return 404, matching POST and the Streamable HTTP session contract. A missing session id stays 400. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: accept bracketed IPv6 origins Node reports URL.hostname as [::1], so an otherwise valid loopback or wildcard Origin was rejected. Compare the address with the brackets removed. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: release rejected HTTP handshakes that never open a session A 406 or 415 from the SDK returns normally before onsessioninitialized. Detach the approval client and close the MCP server when that happens so rejected initializes do not accumulate. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
63 lines
1.7 KiB
TypeScript
63 lines
1.7 KiB
TypeScript
import React from "react";
|
|
import { Check, Copy } from "lucide-react";
|
|
import { cn } from "../../../../lib/utils";
|
|
import { Button } from "../../../ui/button";
|
|
|
|
type CopyableCodeBlockProps = {
|
|
label?: string;
|
|
value: string;
|
|
copyKey: string;
|
|
copied: string | null;
|
|
onCopy: (key: string, text: string) => void;
|
|
copyLabel: string;
|
|
copiedLabel: string;
|
|
emptyLabel?: string;
|
|
className?: string;
|
|
};
|
|
|
|
export const CopyableCodeBlock: React.FC<CopyableCodeBlockProps> = ({
|
|
label,
|
|
value,
|
|
copyKey,
|
|
copied,
|
|
onCopy,
|
|
copyLabel,
|
|
copiedLabel,
|
|
emptyLabel,
|
|
className,
|
|
}) => {
|
|
const display = value || emptyLabel || "";
|
|
const canCopy = Boolean(value);
|
|
const isCopied = copied === copyKey;
|
|
|
|
return (
|
|
<div className={cn("space-y-1.5", className)}>
|
|
{label ? (
|
|
<div className="text-xs font-medium text-muted-foreground">{label}</div>
|
|
) : null}
|
|
<div className="group relative rounded-md border border-border/60 bg-muted/20">
|
|
<pre
|
|
className={cn(
|
|
"max-h-40 overflow-auto whitespace-pre-wrap break-all px-3 py-2.5 pr-11 font-mono text-xs leading-5",
|
|
!value && "text-muted-foreground",
|
|
)}
|
|
>
|
|
{display}
|
|
</pre>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
disabled={!canCopy}
|
|
className="absolute right-1.5 top-1.5 h-7 w-7 p-0 text-muted-foreground hover:text-foreground"
|
|
onClick={() => void onCopy(copyKey, value)}
|
|
aria-label={isCopied ? copiedLabel : copyLabel}
|
|
title={isCopied ? copiedLabel : copyLabel}
|
|
>
|
|
{isCopied ? <Check size={14} className="text-emerald-500" /> : <Copy size={14} />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|