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
2 changes: 1 addition & 1 deletion apps/mnie-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const server = Bun.serve({
if (
url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/.well-known/') ||
['/authorize', '/token', '/register', '/revoke'].includes(url.pathname)
['/mcp', '/authorize', '/token', '/register', '/revoke'].includes(url.pathname)
) {
return app.fetch(request, { server })
}
Expand Down
70 changes: 70 additions & 0 deletions apps/mnie-server/src/routes/bundled-mcp.bun.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect, test } from 'bun:test'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { createDb } from '../db'
import { createApiKey } from '../security/api-keys'

test('the bundled app routes both MCP paths to authenticated tool discovery', async () => {
const directory = mkdtempSync(join(tmpdir(), 'mnie-bundled-mcp-'))
const databasePath = join(directory, 'test.sqlite')
const db = createDb(databasePath)
const { token } = await createApiKey(db, 'bundled MCP test', { scopes: ['read'] })
db.$client.close()
const process = Bun.spawn(
[Bun.which('bun')!, resolve(import.meta.dir, '../../../mnie-app/src/index.ts')],
{
env: {
...Bun.env,
PORT: '0',
MNIE_DATABASE_PATH: databasePath,
MNIE_ORIGIN: 'http://localhost',
},
stdout: 'pipe',
stderr: 'pipe',
},
)
try {
const reader = process.stdout.getReader()
const decoder = new TextDecoder()
let output = ''
let baseURL: string | undefined
const timeout = setTimeout(() => process.kill(), 10_000)
try {
while (!baseURL) {
const chunk = await reader.read()
if (chunk.done) throw new Error(`Bundled app exited before listening: ${output}`)
output += decoder.decode(chunk.value, { stream: true })
baseURL = /mnie listening on (http:\/\/localhost:\d+)/.exec(output)?.[1]
}
} finally {
clearTimeout(timeout)
reader.releaseLock()
}
for (const path of ['/api/mcp', '/mcp']) {
const unauthorized = await fetch(new URL(path, baseURL))
expect(unauthorized.status).toBe(401)
expect(unauthorized.headers.get('content-type')).toContain('application/json')
const client = new Client({ name: 'bundled-discovery', version: '1.0.0' })
try {
await client.connect(
new StreamableHTTPClientTransport(new URL(path, baseURL), {
requestInit: { headers: { Authorization: `Bearer ${token}` } },
}),
)
const { tools } = await client.listTools()
expect(tools.map((tool) => tool.name)).toContain('mnie-workspace-invoke')
const result = await client.callTool({ name: 'mnie-workspace-operations', arguments: {} })
expect(result.isError).not.toBe(true)
} finally {
await client.close()
}
}
} finally {
process.kill()
await process.exited
rmSync(directory, { recursive: true, force: true })
}
}, 15_000)
1 change: 1 addition & 0 deletions apps/mnie-ui/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default defineConfig({
...proxyToServer,
},
'/.well-known': proxyToServer,
'/mcp': proxyToServer,
'/authorize': proxyToServer,
'/token': proxyToServer,
'/register': proxyToServer,
Expand Down
Loading