mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-17 11:23:27 +00:00
Add Azure Connector Namespaces canvas extension 🤖🤖🤖 (#2250)
* Add MCP Connectors (connector-namespaces) canvas extension A Copilot CLI canvas extension for browsing and adding MCP connectors from an Azure Connector Namespace into a Copilot session. Sign-in is dependency-free (OAuth 2.0 auth-code + PKCE via the Azure CLI public client, loopback redirect); network access is restricted to the public Azure Resource Manager endpoint. MIT licensed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update Connector Namespaces canvas extension Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden Connector Namespaces canvas extension Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Sanitize connector icon brand colors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Connector Namespace playground actions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Replace sandbox skill with native tool Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Clarify connector disconnect action Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Color disconnect action red Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Add connector extension plugin manifest Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Address connector canvas review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Remove legacy connector canvas manifest Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Address remaining connector review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Harden connector review fixes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Harden connector convergence Address the latest connector review batch across config persistence, executable trust, reauthentication, JSON-RPC transport, smoke safety, and accessibility. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Use native HTTP connector configs Persist Connector Namespace MCP servers as direct HTTPS entries with API-key headers, remove the stdio unwrap proxy, and exercise the native Streamable HTTP path in smoke coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf * Secure persisted connector state Create the Connector Namespace artifacts directory and saved gateway config with private permissions, and align the reduced-motion regression notes with the static fallback behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf --------- Co-authored-by: Alex Yang <yangalex@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// State management — persists gateway config and tracks added connectors.
|
||||
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { armSegment } from "./armClient.mjs";
|
||||
|
||||
const STORAGE_DIR = join(process.env.COPILOT_HOME || join(homedir(), ".copilot"), "extensions", "connector-namespaces", "artifacts");
|
||||
const CONFIG_FILE = join(STORAGE_DIR, "gateway-config.json");
|
||||
|
||||
// Persisted default for newly opened canvas instances.
|
||||
const addedConnectors = new Map();
|
||||
let savedConfig = null;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Persistent config (gateway selection)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function ensureStorageDir() {
|
||||
mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 });
|
||||
chmodSync(STORAGE_DIR, 0o700);
|
||||
}
|
||||
|
||||
export function isValidConfig(data) {
|
||||
const complete = (
|
||||
data != null &&
|
||||
typeof data === "object" &&
|
||||
typeof data.subscriptionId === "string" && data.subscriptionId.length > 0 &&
|
||||
typeof data.resourceGroup === "string" && data.resourceGroup.length > 0 &&
|
||||
typeof data.gatewayName === "string" && data.gatewayName.length > 0
|
||||
);
|
||||
if (!complete) return false;
|
||||
try {
|
||||
armSegment(data.subscriptionId);
|
||||
armSegment(data.resourceGroup);
|
||||
armSegment(data.gatewayName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function loadSavedConfig() {
|
||||
try {
|
||||
if (existsSync(CONFIG_FILE)) {
|
||||
const data = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
||||
// Only accept a fully-formed config. A shapeless or empty object
|
||||
// (e.g. the legacy "{}" an old clearConfig used to write) must not
|
||||
// masquerade as a valid selection, or the picker gets skipped and
|
||||
// the catalog is fetched with missing coordinates.
|
||||
if (isValidConfig(data)) {
|
||||
savedConfig = data;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
} catch { /* ignore corrupt file */ }
|
||||
savedConfig = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function saveConfig(config) {
|
||||
if (!isValidConfig(config)) {
|
||||
throw new Error("Invalid connector namespace configuration.");
|
||||
}
|
||||
ensureStorageDir();
|
||||
savedConfig = config;
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 0o600 });
|
||||
chmodSync(CONFIG_FILE, 0o600);
|
||||
}
|
||||
|
||||
export function clearConfig() {
|
||||
// Remove the file outright rather than leaving a "{}" stub that a later
|
||||
// loadSavedConfig could misread as a valid selection.
|
||||
try {
|
||||
unlinkSync(CONFIG_FILE);
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") throw error;
|
||||
}
|
||||
savedConfig = null;
|
||||
}
|
||||
|
||||
export function getSavedConfig() {
|
||||
return savedConfig;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Added connectors (session-only, not persisted)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function getAddedConnectors() {
|
||||
return [...addedConnectors.values()];
|
||||
}
|
||||
|
||||
export function addConnector(connector) {
|
||||
if (addedConnectors.has(connector.id)) {
|
||||
return { added: false, reason: "already_added" };
|
||||
}
|
||||
addedConnectors.set(connector.id, {
|
||||
connector,
|
||||
addedAt: new Date().toISOString(),
|
||||
});
|
||||
return { added: true, connector: connector.displayName };
|
||||
}
|
||||
|
||||
export function removeConnector(connectorId) {
|
||||
if (!addedConnectors.has(connectorId)) {
|
||||
return { removed: false, reason: "not_found" };
|
||||
}
|
||||
addedConnectors.delete(connectorId);
|
||||
return { removed: true };
|
||||
}
|
||||
|
||||
export function isConnectorAdded(connectorId) {
|
||||
return addedConnectors.has(connectorId);
|
||||
}
|
||||
Reference in New Issue
Block a user