mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-07 15:46:55 +01:00
EXPERIMENTAL, UNTESTED: explicit session close (DELETE) before reinitializing
Neither of the bridge's two "clear sessionId and reinitialize" paths ever told BC the old session was actually done - they just drop the local sessionId variable and let a fresh session get issued on the next call. The MCP Streamable HTTP transport spec defines an explicit way to end a session: an HTTP DELETE to the endpoint carrying the session's Mcp-Session-Id. The bridge has never called it. Trying this as a candidate fix for the recurring Internal_CompanyNotFound pattern documented in bc-mcp-company-header-must-match-exact-company-name.md: a client-side session reset (clear sessionId + reinitialize) does NOT clear the error, but a manual save on the BC-side MCP Server Configuration record does. If BC's server-side session state is what's actually stuck, an explicit close might do the same job the manual config-save has been doing by accident. Committed before live-testing (not after) specifically so it survives the next sync-bcquality-knowledge.ps1 run instead of being silently overwritten from the old source - this is a durability commit, not a confirmed-fix commit. Best-effort and silent on failure: if BC responds 404/405 (DELETE not implemented), that's evidence for the MS support escalation, not a bug here. Update this commit's status (confirmed working / confirmed no effect / reverted) once tested against a live recurrence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
2ce2697010
commit
4c225c4ac3
1 changed files with 37 additions and 0 deletions
|
|
@ -87,6 +87,41 @@ async function getToken() {
|
||||||
// BC kraever Base64 hvis header-vaerdien har ikke-ASCII (ae/oe/aa).
|
// BC kraever Base64 hvis header-vaerdien har ikke-ASCII (ae/oe/aa).
|
||||||
const enc = v => /[^\x00-\x7F]/.test(v) ? `=?base64?${Buffer.from(v, "utf8").toString("base64")}?=` : v;
|
const enc = v => /[^\x00-\x7F]/.test(v) ? `=?base64?${Buffer.from(v, "utf8").toString("base64")}?=` : v;
|
||||||
|
|
||||||
|
// 2026-08-07: neither of the two "clear sessionId and reinitialize" paths below
|
||||||
|
// ever told BC the old session was actually done -- they just drop the local
|
||||||
|
// sessionId variable and let a fresh session get issued on the next call. The
|
||||||
|
// MCP Streamable HTTP transport spec defines an explicit way to end a session
|
||||||
|
// cleanly: an HTTP DELETE to the same endpoint carrying the session's
|
||||||
|
// Mcp-Session-Id. This bridge has never called it. Trying it now as a
|
||||||
|
// candidate fix for the recurring Internal_CompanyNotFound pattern (retry
|
||||||
|
// after a client-side session reset does NOT clear it; a manual save on the
|
||||||
|
// BC-side MCP Server Configuration record does) -- if BC's server-side session
|
||||||
|
// state is what's actually stuck, telling it explicitly to close might do the
|
||||||
|
// same job the manual config-save has been doing by accident. Best-effort and
|
||||||
|
// silent on failure: if BC responds 404/405 (DELETE not implemented), that is
|
||||||
|
// itself useful evidence for the Microsoft support escalation, not a bug here.
|
||||||
|
async function closeSession(oldSessionId) {
|
||||||
|
if (!oldSessionId) return;
|
||||||
|
try {
|
||||||
|
const tok = await getToken();
|
||||||
|
const r = await fetch(ENDPOINT, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${tok}`,
|
||||||
|
"TenantId": cfg.tenant,
|
||||||
|
"EnvironmentName": cfg.environment,
|
||||||
|
"Company": enc(cfg.company),
|
||||||
|
"ConfigurationName": enc(cfg.config),
|
||||||
|
"Mcp-Session-Id": oldSessionId,
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
||||||
|
});
|
||||||
|
process.stderr.write(`[bc-mcp-bridge] closed session ${oldSessionId}: HTTP ${r.status}\n`);
|
||||||
|
} catch (e) {
|
||||||
|
process.stderr.write(`[bc-mcp-bridge] session close failed (non-fatal): ${e.message || e}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseSSE(text) {
|
function parseSSE(text) {
|
||||||
const msgs = [];
|
const msgs = [];
|
||||||
for (const block of text.split(/\r?\n\r?\n/)) {
|
for (const block of text.split(/\r?\n\r?\n/)) {
|
||||||
|
|
@ -133,6 +168,7 @@ async function forward(msg) {
|
||||||
if (sessionId && msg.method !== "initialize" && lastActivityAt &&
|
if (sessionId && msg.method !== "initialize" && lastActivityAt &&
|
||||||
Date.now() - lastActivityAt > SESSION_IDLE_THRESHOLD_MS) {
|
Date.now() - lastActivityAt > SESSION_IDLE_THRESHOLD_MS) {
|
||||||
process.stderr.write(`[bc-mcp-bridge] session idle ${Math.round((Date.now() - lastActivityAt) / 1000)}s, refreshing proactively\n`);
|
process.stderr.write(`[bc-mcp-bridge] session idle ${Math.round((Date.now() - lastActivityAt) / 1000)}s, refreshing proactively\n`);
|
||||||
|
await closeSession(sessionId);
|
||||||
sessionId = null;
|
sessionId = null;
|
||||||
if (lastInitializeMsg) {
|
if (lastInitializeMsg) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -155,6 +191,7 @@ async function forward(msg) {
|
||||||
// calls never idle long enough to hit this; a long session with gaps
|
// calls never idle long enough to hit this; a long session with gaps
|
||||||
// between calls does.
|
// between calls does.
|
||||||
process.stderr.write(`[bc-mcp-bridge] retrying after: ${e.message || e}\n`);
|
process.stderr.write(`[bc-mcp-bridge] retrying after: ${e.message || e}\n`);
|
||||||
|
await closeSession(sessionId);
|
||||||
sessionId = null;
|
sessionId = null;
|
||||||
// 2026-08-05: clearing sessionId is not enough on its own. BC's own error
|
// 2026-08-05: clearing sessionId is not enough on its own. BC's own error
|
||||||
// is explicit -- "A new session can only be created by an initialize
|
// is explicit -- "A new session can only be created by an initialize
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue