Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,11 @@ export async function buildApp(params: { env: Env; store: RegistryStore }) {
: { mcpServers: {} };

const claudeDesktopConfig = JSON.parse(JSON.stringify(genericConfig));
const claudeDesktopNote = primaryRemote
? 'Claude Desktop remote MCP servers may need to be added via Settings > Connectors. Remote servers might not connect when configured only in claude_desktop_config.json.'
: stdioCommand
? 'Claude Desktop local stdio MCP servers can be configured in claude_desktop_config.json.'
: '';

return {
serverName: entry.server.name,
Expand Down Expand Up @@ -1384,7 +1389,8 @@ export async function buildApp(params: { env: Env; store: RegistryStore }) {
genericMcpHostConfig: {
object: genericConfig,
json: JSON.stringify(genericConfig, null, 2)
}
},
claudeDesktopNote
};
});

Expand Down Expand Up @@ -1439,6 +1445,7 @@ export async function buildApp(params: { env: Env; store: RegistryStore }) {
totalLatestServers,
countRagScoreGte1,
countRagScoreGte25,
reachabilityPolicy: params.env.reachabilityPolicy,
reachabilityCandidates,
reachabilityKnown,
reachabilityTrue,
Expand Down
6 changes: 6 additions & 0 deletions apps/api/tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,9 @@ test('rag install returns copy-ready config object', async () => {
assert.equal(body.serverName, 'example/installable');
assert.equal(body.version, '1.2.3');
assert.equal(body.transport.hasStdio, true);
assert.equal(body.primaryRemoteType, null);
assert.equal(typeof body.claudeDesktopNote, 'string');
assert.equal(body.claudeDesktopNote.includes('claude_desktop_config.json'), true);
assert.equal(typeof body.genericMcpHostConfig?.json, 'string');
assert.equal(body.genericMcpHostConfig.json.includes('mcpServers'), true);
await app.close();
Expand Down Expand Up @@ -841,6 +844,8 @@ test('rag install emits SSE transport and endpoint list for SSE-only servers', a
assert.equal(body.remoteEndpoints[0].type, 'sse');
assert.equal(body.remoteEndpoints[0].url, 'https://example.com/mcp');
assert.equal(body.genericMcpHostConfig.object?.mcpServers?.['example_sse-installable']?.transport, 'sse');
assert.equal(typeof body.claudeDesktopNote, 'string');
assert.equal(body.claudeDesktopNote.includes('Connectors'), true);
await app.close();
});

Expand Down Expand Up @@ -912,6 +917,7 @@ test('rag stats returns freshness and coverage fields', async () => {
assert.equal(typeof body.totalLatestServers, 'number');
assert.equal(typeof body.countRagScoreGte1, 'number');
assert.equal(typeof body.countRagScoreGte25, 'number');
assert.equal(body.reachabilityPolicy, env.reachabilityPolicy);
assert.equal(body.reachabilityCandidates, 2);
assert.equal(body.reachabilityKnown, 1);
assert.equal(body.reachabilityTrue, 1);
Expand Down
1 change: 1 addition & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ For repository workflows that call `/internal/*` routes:
- `sse`: short `GET` with `Accept: text/event-stream`, then immediate body cancel so checks do not hang on streaming responses.
- `/rag/install` now emits remote configs for both `streamable-http` and `sse` endpoints.
Note: SSE support depends on the MCP host/client; Ragmap only emits the correct transport config.
- `/rag/install` also emits `claudeDesktopNote` so UIs can clarify that Claude Desktop remote MCP servers may need to be added via the Connectors UI.

This applies to both scheduled ingest (`/internal/ingest/run`) and scheduled reachability refresh (`/internal/reachability/run`).

Expand Down
37 changes: 36 additions & 1 deletion docs/hosting/api/browse/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
.card button:hover { background: var(--bg); }
.card button.primary { background: var(--accent); color: white; border-color: var(--accent); }
.card button.primary:hover { opacity: 0.92; }
.install-info { margin-top: 10px; border: 1px solid var(--border); border-radius: 10px; padding: 10px; background: #faf8f4; font-size: 12px; color: var(--muted); }
.install-info:empty { display: none; }
.install-info .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.install-info .row:last-child { margin-bottom: 0; }
.transport-badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px; font-size: 11px; border: 1px solid var(--border); color: var(--ink); background: #fff; }
.transport-remote { border-color: #9ec3ff; background: #eef5ff; }
.transport-local { border-color: #b8dcbf; background: #eefaf0; }
.install-info details { margin-top: 6px; }
.install-info summary { cursor: pointer; color: var(--ink); }
.empty { color: var(--muted); padding: 24px; text-align: center; }
.top-cta { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; margin-bottom: 20px; font-size: 13px; }
.top-cta strong { display: block; margin-bottom: 6px; }
Expand Down Expand Up @@ -166,6 +175,30 @@ <h1>RAG servers</h1>
document.getElementById('copy-ragmap-npx').onclick = () => copyText(npxRagmap, 'npx command copied');

function esc(s) { if (s == null) return ''; return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function transportLabel(installData) {
const t = installData && installData.primaryRemoteType;
if (t === 'sse') return { text: 'Remote (SSE)', cls: 'transport-badge transport-remote' };
if (t === 'streamable-http') return { text: 'Remote (Streamable HTTP)', cls: 'transport-badge transport-remote' };
if (installData && installData.transport && installData.transport.hasStdio) {
return { text: 'Local (stdio)', cls: 'transport-badge transport-local' };
}
return { text: 'Transport unknown', cls: 'transport-badge' };
}
function renderInstallInfo(index, installData) {
const host = document.getElementById('install-info-' + index);
if (!host) return;
if (!installData) { host.innerHTML = ''; return; }
const label = transportLabel(installData);
const remoteUrl = installData.remote && installData.remote.url ? installData.remote.url : '';
const note = installData.claudeDesktopNote || '';
host.innerHTML =
'<div class="row"><span class="' + label.cls + '">' + esc(label.text) + '</span>' +
(remoteUrl ? '<span><b>URL:</b> <code>' + esc(remoteUrl) + '</code></span>' : '') +
'</div>' +
(note
? '<details><summary>Claude Desktop note</summary><div>' + esc(note) + '</div></details>'
: '');
}
let lastResults = [];
function render(results) {
lastResults = results;
Expand All @@ -191,7 +224,8 @@ <h1>RAG servers</h1>
'</div>' +
(desc ? '<div class="desc">' + esc(desc) + '</div>' : '') +
(r.categories && r.categories.length ? '<div class="cats">' + r.categories.map(c => '<span class="cat">' + esc(c) + '</span>').join('') + '</div>' : '') +
'<div class="actions">' + actions + '</div></div>';
'<div class="actions">' + actions + '</div>' +
'<div class="install-info" id="install-info-' + i + '"></div></div>';
}).join('');
el.querySelectorAll('.copy-name').forEach(b => b.addEventListener('click', () => { const r = lastResults[parseInt(b.dataset.idx, 10)]; if (r) copyText(r.name || '', 'Name copied'); }));
el.querySelectorAll('.copy-install').forEach(b => b.addEventListener('click', () => { const r = lastResults[parseInt(b.dataset.idx, 10)]; const line = r && installLine(r.server); if (line) copyText(line, 'Install copied'); }));
Expand All @@ -201,6 +235,7 @@ <h1>RAG servers</h1>
fetch('/rag/install?name=' + encodeURIComponent(r.name), { headers: { accept: 'application/json' } })
.then(res => res.ok ? res.json() : Promise.reject(new Error('install config unavailable')))
.then(data => {
renderInstallInfo(parseInt(b.dataset.idx, 10), data);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid rendering install info by mutable list index

The install-info panel is keyed to the transient array index (install-info-${i}), and the async /rag/install callback renders using parseInt(b.dataset.idx, 10); if a user runs a new search before an earlier request returns, the old response can populate transport/URL/note details into a different card that now occupies the same index. This introduces incorrect per-server install guidance in the UI; use a stable key (for example server name) or verify the result set hasn’t changed before updating the panel.

Useful? React with 👍 / 👎.

const text = (data && data.genericMcpHostConfig && data.genericMcpHostConfig.json) || '';
if (!text) throw new Error('install config unavailable');
copyText(text, 'Config copied');
Expand Down