fa-mcp-sdk 0.2.121 → 0.2.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fa-mcp.js +44 -12
- package/cli-template/config/_local.yaml +40 -2
- package/cli-template/config/custom-environment-variables.yaml +5 -1
- package/cli-template/config/default.yaml +35 -5
- package/cli-template/fa-mcp-sdk-spec.md +620 -30
- package/cli-template/package.json +72 -73
- package/dist/core/_types_/TNtlm.d.ts +5 -0
- package/dist/core/_types_/TNtlm.d.ts.map +1 -0
- package/dist/core/_types_/TNtlm.js +2 -0
- package/dist/core/_types_/TNtlm.js.map +1 -0
- package/dist/core/_types_/config.d.ts +75 -0
- package/dist/core/_types_/config.d.ts.map +1 -0
- package/dist/core/_types_/config.js +2 -0
- package/dist/core/_types_/config.js.map +1 -0
- package/dist/core/_types_/types.d.ts +14 -9
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/{token/token-core.d.ts → auth/jwt-validation.d.ts} +2 -2
- package/dist/core/auth/jwt-validation.d.ts.map +1 -0
- package/dist/core/{token/token-core.js → auth/jwt-validation.js} +4 -4
- package/dist/core/auth/jwt-validation.js.map +1 -0
- package/dist/core/auth/middleware.d.ts +19 -0
- package/dist/core/auth/middleware.d.ts.map +1 -0
- package/dist/core/{token/token-auth.js → auth/middleware.js} +52 -55
- package/dist/core/auth/middleware.js.map +1 -0
- package/dist/core/auth/multi-auth.d.ts +23 -0
- package/dist/core/auth/multi-auth.d.ts.map +1 -0
- package/dist/core/auth/multi-auth.js +213 -0
- package/dist/core/auth/multi-auth.js.map +1 -0
- package/dist/core/auth/token-generator/html.d.ts.map +1 -0
- package/dist/core/{token/gen-token-app → auth/token-generator}/html.js +2 -2
- package/dist/core/auth/token-generator/html.js.map +1 -0
- package/dist/core/auth/token-generator/ntlm-auth-options.d.ts.map +1 -0
- package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-auth-options.js +1 -1
- package/dist/core/auth/token-generator/ntlm-auth-options.js.map +1 -0
- package/dist/core/auth/token-generator/ntlm-domain-config.d.ts.map +1 -0
- package/dist/core/auth/token-generator/ntlm-domain-config.js.map +1 -0
- package/dist/core/auth/token-generator/ntlm-integration.d.ts.map +1 -0
- package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-integration.js +4 -4
- package/dist/core/auth/token-generator/ntlm-integration.js.map +1 -0
- package/dist/core/auth/token-generator/ntlm-session-storage.d.ts.map +1 -0
- package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-session-storage.js +1 -1
- package/dist/core/auth/token-generator/ntlm-session-storage.js.map +1 -0
- package/dist/core/auth/token-generator/ntlm-templates.d.ts.map +1 -0
- package/dist/core/auth/token-generator/ntlm-templates.js.map +1 -0
- package/dist/core/{token/gen-token-app/gen-token-server.d.ts → auth/token-generator/server.d.ts} +1 -1
- package/dist/core/auth/token-generator/server.d.ts.map +1 -0
- package/dist/core/{token/gen-token-app/gen-token-server.js → auth/token-generator/server.js} +3 -3
- package/dist/core/auth/token-generator/server.js.map +1 -0
- package/dist/core/auth/types.d.ts +29 -0
- package/dist/core/auth/types.d.ts.map +1 -0
- package/dist/core/auth/types.js +5 -0
- package/dist/core/auth/types.js.map +1 -0
- package/dist/core/bootstrap/init-config.d.ts.map +1 -1
- package/dist/core/bootstrap/init-config.js +4 -0
- package/dist/core/bootstrap/init-config.js.map +1 -1
- package/dist/core/cache/cache.d.ts.map +1 -1
- package/dist/core/cache/cache.js +3 -2
- package/dist/core/cache/cache.js.map +1 -1
- package/dist/core/index.d.ts +6 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/utils/utils.d.ts +6 -0
- package/dist/core/utils/utils.d.ts.map +1 -1
- package/dist/core/utils/utils.js +25 -0
- package/dist/core/utils/utils.js.map +1 -1
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +32 -18
- package/dist/core/web/server-http.js.map +1 -1
- package/package.json +2 -2
- package/cli-template/src/_types_/common.d.ts +0 -27
- package/cli-template/src/api/router.ts +0 -35
- package/cli-template/src/api/swagger.ts +0 -167
- package/cli-template/src/asset/favicon.svg +0 -3
- package/cli-template/src/custom-resources.ts +0 -11
- package/cli-template/src/prompts/agent-brief.ts +0 -8
- package/cli-template/src/prompts/agent-prompt.ts +0 -10
- package/cli-template/src/prompts/custom-prompts.ts +0 -12
- package/cli-template/src/start.ts +0 -71
- package/cli-template/src/tools/handle-tool-call.ts +0 -55
- package/cli-template/src/tools/tools.ts +0 -88
- package/cli-template/tests/jest-simple-reporter.js +0 -10
- package/cli-template/tests/mcp/sse/mcp-sse-client-handling.md +0 -111
- package/cli-template/tests/mcp/sse/test-sse-npm-package.js +0 -96
- package/cli-template/tests/mcp/test-cases.js +0 -143
- package/cli-template/tests/mcp/test-http.js +0 -63
- package/cli-template/tests/mcp/test-sse.js +0 -67
- package/cli-template/tests/mcp/test-stdio.js +0 -78
- package/cli-template/tests/utils.ts +0 -154
- package/dist/core/token/gen-token-app/gen-token-server.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/gen-token-server.js.map +0 -1
- package/dist/core/token/gen-token-app/html.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/html.js.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-auth-options.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-auth-options.js.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-domain-config.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-domain-config.js.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-integration.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-integration.js.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-session-storage.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-session-storage.js.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-templates.d.ts.map +0 -1
- package/dist/core/token/gen-token-app/ntlm-templates.js.map +0 -1
- package/dist/core/token/i-token.d.ts +0 -13
- package/dist/core/token/i-token.d.ts.map +0 -1
- package/dist/core/token/i-token.js +0 -2
- package/dist/core/token/i-token.js.map +0 -1
- package/dist/core/token/token-auth.d.ts +0 -17
- package/dist/core/token/token-auth.d.ts.map +0 -1
- package/dist/core/token/token-auth.js.map +0 -1
- package/dist/core/token/token-core.d.ts.map +0 -1
- package/dist/core/token/token-core.js.map +0 -1
- /package/dist/core/{token/gen-token-app → auth/token-generator}/html.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-auth-options.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-domain-config.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-domain-config.js +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-integration.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-session-storage.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-templates.d.ts +0 -0
- /package/dist/core/{token/gen-token-app → auth/token-generator}/ntlm-templates.js +0 -0
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Import all project data from existing files
|
|
2
|
-
// @ts-ignore
|
|
3
|
-
import { appConfig, initMcpServer, McpServerData, getAsset } from 'fa-mcp-sdk';
|
|
4
|
-
import { tools } from './tools/tools.js';
|
|
5
|
-
import { handleToolCall } from './tools/handle-tool-call.js';
|
|
6
|
-
import { AGENT_BRIEF } from './prompts/agent-brief.js';
|
|
7
|
-
import { AGENT_PROMPT } from './prompts/agent-prompt.js';
|
|
8
|
-
import { customPrompts } from './prompts/custom-prompts.js';
|
|
9
|
-
import { customResources } from './custom-resources.js';
|
|
10
|
-
import { apiRouter, endpointsOn404 } from './api/router.js';
|
|
11
|
-
import { swagger } from './api/swagger.js';
|
|
12
|
-
|
|
13
|
-
const isConsulProd = (process.env.NODE_CONSUL_ENV || process.env.NODE_ENV) === 'production';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Main function that assembles all project data and starts the MCP server
|
|
17
|
-
*/
|
|
18
|
-
const startProject = async (): Promise<void> => {
|
|
19
|
-
// Read favicon from assets
|
|
20
|
-
const favicon = getAsset('favicon.svg')!;
|
|
21
|
-
|
|
22
|
-
// Assemble all data to pass to the core
|
|
23
|
-
const serverData: McpServerData = {
|
|
24
|
-
// MCP components
|
|
25
|
-
tools,
|
|
26
|
-
toolHandler: handleToolCall,
|
|
27
|
-
|
|
28
|
-
// Prompts
|
|
29
|
-
agentBrief: AGENT_BRIEF,
|
|
30
|
-
agentPrompt: AGENT_PROMPT,
|
|
31
|
-
customPrompts,
|
|
32
|
-
requiredHttpHeaders: [{ name: 'Authorization', description: 'JWT Token issued on request' }],
|
|
33
|
-
// Resources
|
|
34
|
-
customResources,
|
|
35
|
-
|
|
36
|
-
// HTTP components
|
|
37
|
-
httpComponents: {
|
|
38
|
-
apiRouter,
|
|
39
|
-
endpointsOn404,
|
|
40
|
-
swagger: {
|
|
41
|
-
swaggerSpecs: swagger.swaggerSpecs,
|
|
42
|
-
swaggerUi: swagger.swaggerUi,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
// Assets
|
|
47
|
-
assets: {
|
|
48
|
-
favicon,
|
|
49
|
-
maintainerHtml: '<a href="https://support.com/page/2805" target="_blank" rel="noopener" class="clickable">Support</a>',
|
|
50
|
-
},
|
|
51
|
-
// Function to get Consul UI address (if consul enabled: consul.service.enable = true)
|
|
52
|
-
getConsulUIAddress: (serviceId: string) => {
|
|
53
|
-
const { agent } = appConfig.consul || {};
|
|
54
|
-
if (!agent?.dev?.host || !agent?.prd?.host) {
|
|
55
|
-
return '--consul-ui-not-configured--';
|
|
56
|
-
}
|
|
57
|
-
return `${isConsulProd
|
|
58
|
-
? `https://${agent.prd.host}/ui/dc-msk-infra`
|
|
59
|
-
: `https://${agent.dev.host}/ui/dc-dev`
|
|
60
|
-
}/services/${serviceId}/instances`;
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Start MCP server with assembled data
|
|
65
|
-
await initMcpServer(serverData);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
startProject().catch(error => {
|
|
69
|
-
console.error('Failed to start project:', error);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { logger as lgr, formatToolResult, ToolExecutionError } from 'fa-mcp-sdk';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
|
|
4
|
-
const logger = lgr.getSubLogger({ name: chalk.bgGrey('tools') });
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Template tool handler - customize this for your specific tools
|
|
8
|
-
* This handles MCP tool execution requests
|
|
9
|
-
*/
|
|
10
|
-
export const handleToolCall = async (params: { name: string, arguments?: any }): Promise<any> => {
|
|
11
|
-
const { name, arguments: args } = params;
|
|
12
|
-
|
|
13
|
-
logger.info(`Tool called: ${name}`);
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// TODO: Implement your tool routing logic here
|
|
17
|
-
switch (name) {
|
|
18
|
-
case 'example_tool':
|
|
19
|
-
return await handleExampleTool(args);
|
|
20
|
-
|
|
21
|
-
default:
|
|
22
|
-
throw new ToolExecutionError(name, `Unknown tool: ${name}`);
|
|
23
|
-
}
|
|
24
|
-
} catch (error) {
|
|
25
|
-
logger.error(`Tool execution failed for ${name}:`, error);
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Example tool implementation
|
|
32
|
-
* Replace this with your actual tool logic
|
|
33
|
-
*/
|
|
34
|
-
async function handleExampleTool (args: any): Promise<string> {
|
|
35
|
-
const { query } = args || {};
|
|
36
|
-
|
|
37
|
-
if (!query) {
|
|
38
|
-
throw new ToolExecutionError('example_tool', 'Query parameter is required');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Simulate some work
|
|
42
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
43
|
-
|
|
44
|
-
const result = {
|
|
45
|
-
message: `Processed query: ${query}`,
|
|
46
|
-
timestamp: new Date().toISOString(),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return formatToolResult(result);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// TODO: Add more tool handlers here
|
|
53
|
-
// async function handleAnotherTool(args: any): Promise<string> {
|
|
54
|
-
// // Your implementation
|
|
55
|
-
// }
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { IToolInputSchema, IToolProperties } from 'fa-mcp-sdk';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Template tools configuration for MCP Server
|
|
6
|
-
* Define your tools according to your server's functionality
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const getGenericInputSchema = (
|
|
11
|
-
queryDescription?: string,
|
|
12
|
-
additionalProperties?: IToolProperties,
|
|
13
|
-
): IToolInputSchema => {
|
|
14
|
-
const properties = {
|
|
15
|
-
query: {
|
|
16
|
-
type: 'string',
|
|
17
|
-
description: queryDescription || 'Input query or text',
|
|
18
|
-
},
|
|
19
|
-
...additionalProperties,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
type: 'object',
|
|
24
|
-
properties,
|
|
25
|
-
required: ['query'],
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const getSearchInputSchema = (): IToolInputSchema => {
|
|
30
|
-
return {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
query: {
|
|
34
|
-
type: 'string',
|
|
35
|
-
description: 'Search query',
|
|
36
|
-
},
|
|
37
|
-
limit: {
|
|
38
|
-
type: 'number',
|
|
39
|
-
description: 'Maximum number of results to return (1-100, default: 20)',
|
|
40
|
-
minimum: 1,
|
|
41
|
-
maximum: 100,
|
|
42
|
-
},
|
|
43
|
-
threshold: {
|
|
44
|
-
type: 'number',
|
|
45
|
-
description: 'Minimum similarity threshold (0-1)',
|
|
46
|
-
minimum: 0,
|
|
47
|
-
maximum: 1,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
required: ['query'],
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Template tools - customize according to your needs
|
|
55
|
-
export const tools: Tool[] = [
|
|
56
|
-
{
|
|
57
|
-
name: 'example_tool',
|
|
58
|
-
description: 'Example tool that processes text input. Replace with your actual tools.',
|
|
59
|
-
inputSchema: getGenericInputSchema('Text to process'),
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'example_search',
|
|
63
|
-
description: 'Example search tool with pagination and filtering. Template for search-based tools.',
|
|
64
|
-
inputSchema: getSearchInputSchema(),
|
|
65
|
-
},
|
|
66
|
-
// TODO: Add your actual tools here
|
|
67
|
-
// {
|
|
68
|
-
// name: 'your_tool_name',
|
|
69
|
-
// description: 'Description of what your tool does',
|
|
70
|
-
// inputSchema: getGenericInputSchema('Your query description', {
|
|
71
|
-
// // additional parameters
|
|
72
|
-
// param1: {
|
|
73
|
-
// type: 'string',
|
|
74
|
-
// description: 'Description of param1',
|
|
75
|
-
// },
|
|
76
|
-
// }),
|
|
77
|
-
// },
|
|
78
|
-
];
|
|
79
|
-
|
|
80
|
-
// Helper to get tool by name
|
|
81
|
-
export const getToolByName = (name: string): Tool | undefined => {
|
|
82
|
-
return tools.find(tool => tool.name === name);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Helper to get all tool names
|
|
86
|
-
export const getToolNames = (): string[] => {
|
|
87
|
-
return tools.map(tool => tool.name);
|
|
88
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default class SimpleReporter {
|
|
2
|
-
onRunComplete (contexts, runResults) {
|
|
3
|
-
const { numPassedTests, numFailedTests, numPendingTests } = runResults;
|
|
4
|
-
const total = numPassedTests + numFailedTests + numPendingTests;
|
|
5
|
-
const actualSuccess = numFailedTests === 0 && total > 0;
|
|
6
|
-
|
|
7
|
-
console.log(`\nTest Results: ${numPassedTests} passed, ${numFailedTests} failed, ${numPendingTests} skipped (${total} total)`);
|
|
8
|
-
console.log(`Status: ${actualSuccess ? 'PASSED' : 'FAILED'}`);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# MCP SSE Client - Preventing unhandledRejection
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
|
|
5
|
-
When using `fa-mcp-sdk` as an npm package in tests where `McpSseClient` is imported, `unhandledRejection` errors may occur. This happens because MCP errors are handled asynchronously through SSE (Server-Sent Events) and may not be properly caught in the test context.
|
|
6
|
-
|
|
7
|
-
## Why this happens
|
|
8
|
-
|
|
9
|
-
1. **Local tests**: Run in the same process as the MCP server
|
|
10
|
-
2. **NPM package**: The test runs in a separate process
|
|
11
|
-
3. **SSE asynchronicity**: Errors may occur after the promise is created but before they are handled
|
|
12
|
-
|
|
13
|
-
## Solutions
|
|
14
|
-
|
|
15
|
-
### Solution 1: Use a static method (recommended)
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
import { McpSseClient } from 'fa-mcp-sdk';
|
|
19
|
-
|
|
20
|
-
// Create a client with automatic unhandledRejection handling
|
|
21
|
-
const client = McpSseClient.createWithErrorHandler('http://localhost:3000');
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const response = await client.callTool('execute_sql_query', { sql: test.sql });
|
|
25
|
-
console.log('✅ Success:', response);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.log('❌ Error:', error.message);
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Solution 2: Global unhandledRejection handler
|
|
32
|
-
|
|
33
|
-
Add at the beginning of the test file:
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
// Global handling of MCP errors
|
|
37
|
-
process.on('unhandledRejection', (reason) => {
|
|
38
|
-
if (typeof reason === 'object' && reason?.message?.includes('MCP Error:')) {
|
|
39
|
-
// Ignore MCP errors — they are handled in try-catch
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Or using the built-in method
|
|
45
|
-
import { McpSseClient } from 'fa-mcp-sdk';
|
|
46
|
-
McpSseClient.setupGlobalErrorHandler();
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Solution 3: Promise with error handling
|
|
50
|
-
|
|
51
|
-
```javascript
|
|
52
|
-
import { McpSseClient } from 'fa-mcp-sdk';
|
|
53
|
-
|
|
54
|
-
const client = new McpSseClient('http://localhost:3000');
|
|
55
|
-
|
|
56
|
-
// Wrap all calls in an additional catch
|
|
57
|
-
async function safeCallTool(toolName, args) {
|
|
58
|
-
try {
|
|
59
|
-
return await client.callTool(toolName, args);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Additional handling to prevent unhandledRejection
|
|
62
|
-
if (error.message.includes('MCP Error:')) {
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
throw new Error(`Unexpected error: ${error.message}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Full test example
|
|
71
|
-
|
|
72
|
-
```javascript
|
|
73
|
-
import { McpSseClient } from 'fa-mcp-sdk';
|
|
74
|
-
|
|
75
|
-
async function testToolExecution() {
|
|
76
|
-
// Create a client with error handling
|
|
77
|
-
const client = McpSseClient.createWithErrorHandler('http://localhost:3000');
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
// Test successful execution
|
|
81
|
-
const successResponse = await client.callTool('example_tool', { query: 'ping' });
|
|
82
|
-
console.log('✅ Tool executed successfully:', successResponse);
|
|
83
|
-
|
|
84
|
-
// Test error handling (as in your example)
|
|
85
|
-
try {
|
|
86
|
-
await client.callTool('example_tool', {}); // No query — should error
|
|
87
|
-
console.log('❌ Expected error but got success');
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.log('✅ Expected error caught:', error.message);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error('❌ Unexpected test error:', error);
|
|
94
|
-
} finally {
|
|
95
|
-
await client.close();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
testToolExecution();
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Important notes
|
|
103
|
-
|
|
104
|
-
1. **For npm packages only**: The issue usually does not occur in local tests
|
|
105
|
-
2. **MCP errors**: The handler filters only errors containing 'MCP Error:'
|
|
106
|
-
3. **Global handler**: Installed once per process
|
|
107
|
-
4. **Resources**: Don’t forget to call `client.close()` for cleanup
|
|
108
|
-
|
|
109
|
-
## Technical details
|
|
110
|
-
|
|
111
|
-
The issue arises because SSE errors are handled in `McpSseClient.handleSseEvent()` and may cause a promise rejection that is not yet caught at the moment the error occurs. New methods provide an additional layer of handling to prevent unhandledRejection.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Example of using fa-mcp-sdk as an npm package without unhandledRejection issues
|
|
5
|
-
*
|
|
6
|
-
* To use this example:
|
|
7
|
-
* 1. Install fa-mcp-sdk: npm install fa-mcp-sdk
|
|
8
|
-
* 2. Start MCP server: npm run template:start (in fa-mcp-sdk project)
|
|
9
|
-
* 3. Run this example: node test-npm-package.js
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { McpSseClient } from 'fa-mcp-sdk';
|
|
13
|
-
|
|
14
|
-
const SERVER_URL = 'http://localhost:{{port}}';
|
|
15
|
-
|
|
16
|
-
async function testMcpClient () {
|
|
17
|
-
console.log('🧪 Testing MCP client as npm package');
|
|
18
|
-
console.log('='.repeat(50));
|
|
19
|
-
|
|
20
|
-
// Use the new method that handles unhandledRejection
|
|
21
|
-
const client = McpSseClient.createWithErrorHandler(SERVER_URL);
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Health check
|
|
25
|
-
console.log('1. Health check...');
|
|
26
|
-
try {
|
|
27
|
-
const health = await client.health();
|
|
28
|
-
console.log('✅ Health check passed:', health.status);
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.log('⚠️ Health check failed:', error.message);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Initialize
|
|
34
|
-
console.log('2. Initializing...');
|
|
35
|
-
await client.sendRequest('initialize', {
|
|
36
|
-
protocolVersion: '2024-11-05',
|
|
37
|
-
capabilities: { tools: {} },
|
|
38
|
-
clientInfo: { name: 'npm-test', version: '1.0.0' },
|
|
39
|
-
});
|
|
40
|
-
console.log('✅ Initialized successfully');
|
|
41
|
-
|
|
42
|
-
// List tools
|
|
43
|
-
console.log('3. Listing tools...');
|
|
44
|
-
const tools = await client.listTools();
|
|
45
|
-
const toolNames = tools.tools?.map(t => t.name) || [];
|
|
46
|
-
console.log('✅ Available tools:', toolNames.join(', '));
|
|
47
|
-
|
|
48
|
-
// Test successful tool call
|
|
49
|
-
console.log('4. Testing successful tool call...');
|
|
50
|
-
try {
|
|
51
|
-
const response = await client.callTool('example_tool', { query: 'ping' });
|
|
52
|
-
console.log('✅ Tool call successful:', response.result?.structuredContent?.message || response.result?.content?.[0]?.text);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.log('❌ Tool call failed:', error.message);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Test expected error (like the test case in test-cases.js)
|
|
58
|
-
console.log('5. Testing expected error handling...');
|
|
59
|
-
try {
|
|
60
|
-
await client.callTool('example_tool', {}); // Missing query parameter
|
|
61
|
-
console.log('❌ Expected error but got success');
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.log('✅ Expected error caught:', error.message);
|
|
64
|
-
// This error should NOT cause unhandledRejection
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Test with invalid tool name
|
|
68
|
-
console.log('6. Testing invalid tool name...');
|
|
69
|
-
try {
|
|
70
|
-
await client.callTool('nonexistent_tool', {});
|
|
71
|
-
console.log('❌ Expected error but got success');
|
|
72
|
-
} catch (error) {
|
|
73
|
-
console.log('✅ Expected error caught:', error.message);
|
|
74
|
-
// This error should NOT cause unhandledRejection
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
console.log('\n🎉 All tests completed successfully!');
|
|
78
|
-
console.log('✨ No unhandledRejection errors occurred');
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error('❌ Unexpected error:', error.message);
|
|
82
|
-
} finally {
|
|
83
|
-
await client.close();
|
|
84
|
-
console.log('🔚 Client closed');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Global handler for any unexpected unhandled rejections
|
|
89
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
90
|
-
console.error('💥 UNHANDLED REJECTION (this should not happen):');
|
|
91
|
-
console.error('Reason:', reason);
|
|
92
|
-
console.error('Promise:', promise);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
testMcpClient().catch(console.error);
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shared test cases for the template MCP server (src/template)
|
|
5
|
-
* Covers: prompts, resources, tools
|
|
6
|
-
*
|
|
7
|
-
* Each test case is a function(client) -> Promise<{ name, passed, details? }>
|
|
8
|
-
* where client provides methods:
|
|
9
|
-
* - listPrompts(), getPrompt(name, args?)
|
|
10
|
-
* - listResources(), readResource(uri)
|
|
11
|
-
* - listTools(), callTool(name, args?)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const ok = (name, details) => ({ name, passed: true, details });
|
|
15
|
-
const fail = (name, details) => ({ name, passed: false, details });
|
|
16
|
-
|
|
17
|
-
// Utility: extract system text from prompts/get response
|
|
18
|
-
const extractPromptText = (resp) => {
|
|
19
|
-
// resp may be raw result or wrapped; support both shapes used by clients
|
|
20
|
-
const r = resp?.result || resp;
|
|
21
|
-
const msg = r?.messages?.[0];
|
|
22
|
-
const text = msg?.content?.text || msg?.content?.[0]?.text || r?.messages?.[0]?.content?.[0]?.text;
|
|
23
|
-
return typeof text === 'string' ? text : undefined;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const TEMPLATE_TESTS = {
|
|
27
|
-
prompts: [
|
|
28
|
-
async (client) => {
|
|
29
|
-
const name = 'List prompts contains agent_brief and agent_prompt';
|
|
30
|
-
try {
|
|
31
|
-
const list = await client.listPrompts();
|
|
32
|
-
const prompts = list?.prompts || list;
|
|
33
|
-
const names = Array.isArray(prompts) ? prompts.map(p => p.name) : [];
|
|
34
|
-
const okBrief = names.includes('agent_brief');
|
|
35
|
-
const okPrompt = names.includes('agent_prompt');
|
|
36
|
-
return (okBrief && okPrompt) ? ok(name, { names }) : fail(name, { names });
|
|
37
|
-
} catch (e) {
|
|
38
|
-
return fail(name, { error: e?.message });
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
async (client) => {
|
|
42
|
-
const name = 'Get agent_brief returns text';
|
|
43
|
-
try {
|
|
44
|
-
const resp = await client.getPrompt('agent_brief');
|
|
45
|
-
const text = extractPromptText(resp);
|
|
46
|
-
return text ? ok(name, { text }) : fail(name, { text });
|
|
47
|
-
} catch (e) {
|
|
48
|
-
return fail(name, { error: e?.message });
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
async (client) => {
|
|
52
|
-
const name = 'Get agent_prompt returns text';
|
|
53
|
-
try {
|
|
54
|
-
const resp = await client.getPrompt('agent_prompt');
|
|
55
|
-
const text = extractPromptText(resp);
|
|
56
|
-
return text ? ok(name, { text }) : fail(name, { text });
|
|
57
|
-
} catch (e) {
|
|
58
|
-
return fail(name, { error: e?.message });
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
async (client) => {
|
|
62
|
-
const name = 'Get custom_prompt returns dynamic text';
|
|
63
|
-
try {
|
|
64
|
-
const resp = await client.getPrompt('custom_prompt', { sample: '1' });
|
|
65
|
-
const text = extractPromptText(resp);
|
|
66
|
-
const hasWord = typeof text === 'string' && text.includes('Custom prompt content');
|
|
67
|
-
return hasWord ? ok(name, { text }) : fail(name, { text });
|
|
68
|
-
} catch (e) {
|
|
69
|
-
return fail(name, { error: e?.message });
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
|
|
74
|
-
resources: [
|
|
75
|
-
async (client) => {
|
|
76
|
-
const name = 'List resources contains custom-resource://resource1';
|
|
77
|
-
try {
|
|
78
|
-
const list = await client.listResources();
|
|
79
|
-
const resources = list?.resources || list;
|
|
80
|
-
const uris = Array.isArray(resources) ? resources.map(r => r.uri) : [];
|
|
81
|
-
const found = uris.includes('custom-resource://resource1');
|
|
82
|
-
return found ? ok(name, { uris }) : fail(name, { uris });
|
|
83
|
-
} catch (e) {
|
|
84
|
-
return fail(name, { error: e?.message });
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
async (client) => {
|
|
88
|
-
const name = 'Read resource custom-resource://resource1 returns content';
|
|
89
|
-
try {
|
|
90
|
-
const resp = await client.readResource('custom-resource://resource1');
|
|
91
|
-
// Different clients return differently; normalize
|
|
92
|
-
const r = resp?.result || resp;
|
|
93
|
-
const text = r?.resource?.text || r?.contents?.[0]?.text || r?.text || r?.resource?.content;
|
|
94
|
-
const okText = typeof text === 'string' && text.length > 0;
|
|
95
|
-
return okText ? ok(name, { text }) : fail(name, { response: r });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
return fail(name, { error: e?.message });
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
|
|
102
|
-
tools: [
|
|
103
|
-
async (client) => {
|
|
104
|
-
const name = 'List tools contains example_tool and example_search';
|
|
105
|
-
try {
|
|
106
|
-
const list = await client.listTools();
|
|
107
|
-
const tools = list?.tools || list;
|
|
108
|
-
const names = Array.isArray(tools) ? tools.map(t => t.name) : [];
|
|
109
|
-
const ok1 = names.includes('example_tool');
|
|
110
|
-
const ok2 = names.includes('example_search');
|
|
111
|
-
return (ok1 && ok2) ? ok(name, { names }) : fail(name, { names });
|
|
112
|
-
} catch (e) {
|
|
113
|
-
return fail(name, { error: e?.message });
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
async (client) => {
|
|
117
|
-
const name = 'Call example_tool returns formatted result';
|
|
118
|
-
try {
|
|
119
|
-
const resp = await client.callTool('example_tool', { query: 'ping' });
|
|
120
|
-
const r = resp?.result || resp;
|
|
121
|
-
// Both structuredContent and text are acceptable; check message echo
|
|
122
|
-
const structured = r?.structuredContent;
|
|
123
|
-
const text = r?.content?.[0]?.text;
|
|
124
|
-
const hasProcessed = (structured && structured.message?.includes('Processed query'))
|
|
125
|
-
|| (typeof text === 'string' && text.includes('Processed query'));
|
|
126
|
-
return hasProcessed ? ok(name, { response: r }) : fail(name, { response: r });
|
|
127
|
-
} catch (e) {
|
|
128
|
-
return fail(name, { error: e?.message });
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
async (client) => {
|
|
132
|
-
const name = 'Call example_tool without query should fail';
|
|
133
|
-
try {
|
|
134
|
-
await client.callTool('example_tool', {});
|
|
135
|
-
return fail(name, { error: 'Expected failure, got success' });
|
|
136
|
-
} catch (e) {
|
|
137
|
-
return ok(name, { error: e?.message });
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export default TEMPLATE_TESTS;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* HTTP transport tests for the template MCP server (src/template)
|
|
5
|
-
* Uses McpHttpClient (simple POST requests)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { appConfig, McpHttpClient } from 'fa-mcp-sdk';
|
|
9
|
-
import TEMPLATE_TESTS from './test-cases.js';
|
|
10
|
-
|
|
11
|
-
const baseUrl = (process.env.TEST_MCP_SERVER_URL || `http://localhost:${appConfig.webServer.port}`).replace(/\/+$/,'');
|
|
12
|
-
|
|
13
|
-
async function runTestGroup (title, tests, client) {
|
|
14
|
-
console.log(`\n${title}:`);
|
|
15
|
-
let passed = 0;
|
|
16
|
-
for (const test of tests) {
|
|
17
|
-
try {
|
|
18
|
-
const res = await test(client);
|
|
19
|
-
if (res.passed) {
|
|
20
|
-
console.log(` ✅ ${res.name}`);
|
|
21
|
-
passed++;
|
|
22
|
-
} else {
|
|
23
|
-
console.log(` ❌ ${res.name}`);
|
|
24
|
-
if (res.details) {
|
|
25
|
-
console.log(' ', res.details);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.log(` ❌ ${(await test).name || 'test'}:`, e.message);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
console.log(` Result: ${passed}/${tests.length} passed`);
|
|
33
|
-
return passed;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function main () {
|
|
37
|
-
console.log('🧪 HTTP tests for template MCP server');
|
|
38
|
-
console.log('='.repeat(60));
|
|
39
|
-
|
|
40
|
-
const client = new McpHttpClient(baseUrl);
|
|
41
|
-
try {
|
|
42
|
-
await client.initialize({
|
|
43
|
-
protocolVersion: '2024-11-05',
|
|
44
|
-
capabilities: { tools: {} },
|
|
45
|
-
clientInfo: { name: 'http-test', version: '1.0.0' },
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const p1 = await runTestGroup('Prompts', TEMPLATE_TESTS.prompts, client);
|
|
49
|
-
const p2 = await runTestGroup('Resources', TEMPLATE_TESTS.resources, client);
|
|
50
|
-
const p3 = await runTestGroup('Tools', TEMPLATE_TESTS.tools, client);
|
|
51
|
-
|
|
52
|
-
const total = TEMPLATE_TESTS.prompts.length + TEMPLATE_TESTS.resources.length + TEMPLATE_TESTS.tools.length;
|
|
53
|
-
const sum = p1 + p2 + p3;
|
|
54
|
-
console.log(`\nSummary: ${sum}/${total} tests passed`);
|
|
55
|
-
} finally {
|
|
56
|
-
await client.close().catch(() => undefined);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
main().catch((e) => {
|
|
61
|
-
console.error('Test failed:', e?.message || e);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
});
|