|
|
@@ -37,6 +37,14 @@ function startUpstream(): Promise<string> {
|
|
|
mcp.registerTool('echo', { description: '原样返回', inputSchema: { text: z.string() }, annotations: { readOnlyHint: true } }, async ({ text }) => ({ content: [{ type: 'text', text: `echo:${text}` }] }))
|
|
|
mcp.registerTool('write_note', { description: '写一条笔记', inputSchema: { note: z.string() } }, async ({ note }) => ({ content: [{ type: 'text', text: `saved:${note}` }] }))
|
|
|
mcp.registerTool('big', { description: '很长的结果', inputSchema: {}, annotations: { readOnlyHint: true } }, async () => ({ content: [{ type: 'text', text: 'x'.repeat(150_000) }] }))
|
|
|
+ mcp.registerTool('export', { description: '导出文件', inputSchema: {}, annotations: { readOnlyHint: true } }, async () => ({
|
|
|
+ content: [
|
|
|
+ { type: 'text', text: '共 3 行' },
|
|
|
+ { type: 'resource', resource: { uri: 'fwh://export/power flow/SN1_2026.csv', mimeType: 'text/csv', text: 'ts,pv\n1,2\n3,4\n' } },
|
|
|
+ { type: 'resource', resource: { uri: 'fwh://export/../../wave.bin?x=1', mimeType: 'application/octet-stream', blob: Buffer.from([0, 1, 2, 255]).toString('base64') } },
|
|
|
+ { type: 'resource', resource: { uri: 'fwh://export/power flow/SN1_2026.csv', mimeType: 'text/csv', text: 'again' } },
|
|
|
+ ],
|
|
|
+ }))
|
|
|
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })
|
|
|
res.on('close', () => { void t.close(); void mcp.close() })
|
|
|
await mcp.connect(t)
|
|
|
@@ -90,7 +98,7 @@ describe('第三方 MCP(远程 HTTP)', () => {
|
|
|
it('测试连接、登记;密钥不返回,更新时可沿用', async () => {
|
|
|
const t = await app.mcpServers.test(httpInput(), null)
|
|
|
expect(t).toMatchObject({ ok: true })
|
|
|
- expect(t.tools.map(x => [x.name, x.readOnly, x.policy])).toEqual([['echo', true, 'auto'], ['write_note', false, 'ask'], ['big', true, 'auto']])
|
|
|
+ expect(t.tools.map(x => [x.name, x.readOnly, x.policy])).toEqual([['echo', true, 'auto'], ['write_note', false, 'ask'], ['big', true, 'auto'], ['export', true, 'auto']])
|
|
|
const bad = await app.mcpServers.test(httpInput({ headers: [{ name: 'Authorization', value: 'Bearer wrong' }] }), null)
|
|
|
expect(bad.ok).toBe(false)
|
|
|
|
|
|
@@ -99,7 +107,7 @@ describe('第三方 MCP(远程 HTTP)', () => {
|
|
|
expect(JSON.stringify(s)).not.toContain('up-secret')
|
|
|
const updated = app.mcpServers.update(s.id, { ...httpInput({ title: '演示服务2' }), headers: [{ name: 'Authorization', value: null }] })
|
|
|
expect(updated.title).toBe('演示服务2')
|
|
|
- expect((await app.mcpServers.refresh(s.id)).tools).toHaveLength(3)
|
|
|
+ expect((await app.mcpServers.refresh(s.id)).tools).toHaveLength(4)
|
|
|
expect(() => app.mcpServers.create(httpInput())).toThrow(/已被使用/)
|
|
|
expect(() => app.mcpServers.create(httpInput({ name: 'Bad Name' }))).toThrow(/名称/)
|
|
|
expect(() => app.mcpServers.create(httpInput({ name: 'x2', headers: [{ name: 'Authorization', value: null }] }))).toThrow(/请填写/)
|
|
|
@@ -133,6 +141,18 @@ describe('第三方 MCP(远程 HTTP)', () => {
|
|
|
expect(big.length).toBeLessThan(101_000)
|
|
|
const saved = /保存到 (\S+))/.exec(big)![1]!
|
|
|
expect(fs.readFileSync(path.join(ctx.workspaceDir, saved), 'utf8')).toHaveLength(150_000)
|
|
|
+
|
|
|
+ // 嵌入资源一律存入工作区,只返回路径;文件名取 URI 最后一段,重名加序号,不会越出 .agent/mcp
|
|
|
+ const exported = (await c.callTool({ name: 'demo__export', arguments: {} })).content as { type: string; text: string }[]
|
|
|
+ expect(exported.map(x => x.type)).toEqual(['text', 'text', 'text', 'text'])
|
|
|
+ expect(exported[0]!.text).toBe('共 3 行')
|
|
|
+ const paths = exported.slice(1).map(x => /^已保存到 (\S+?)(/.exec(x.text)![1]!)
|
|
|
+ expect(paths).toEqual([path.join('.agent', 'mcp', 'SN1_2026.csv'), path.join('.agent', 'mcp', 'wave.bin'), path.join('.agent', 'mcp', 'SN1_2026-2.csv')])
|
|
|
+ expect(exported[1]!.text).toContain('text/csv')
|
|
|
+ expect(exported.map(x => x.text).join('')).not.toContain('ts,pv')
|
|
|
+ expect(fs.readFileSync(path.join(ctx.workspaceDir, paths[0]!), 'utf8')).toBe('ts,pv\n1,2\n3,4\n')
|
|
|
+ expect([...fs.readFileSync(path.join(ctx.workspaceDir, paths[1]!))]).toEqual([0, 1, 2, 255])
|
|
|
+ expect(fs.readFileSync(path.join(ctx.workspaceDir, paths[2]!), 'utf8')).toBe('again')
|
|
|
await c.close()
|
|
|
|
|
|
const ro = await gateway(runCtx({ mcpServerIds: [id], permissionMode: 'readonly' }))
|