@pagopa/dx-mcpserver 0.0.2 → 0.0.3
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/README.md +3 -3
- package/dist/auth/__tests__/githubAuth.test.js +12 -3
- package/dist/auth/github.js +4 -3
- package/dist/config/__tests__/awsConfig.test.js +1 -8
- package/dist/config/aws.js +4 -3
- package/dist/config/logging.js +56 -0
- package/dist/config/monitoring.js +26 -0
- package/dist/decorators/promptUsageMonitoring.js +31 -0
- package/dist/decorators/toolUsageMonitoring.js +56 -0
- package/dist/index.js +23 -5
- package/dist/services/__tests__/bedrock.test.js +13 -8
- package/dist/services/bedrock.js +2 -1
- package/dist/tools/QueryPagoPADXDocumentation.js +15 -8
- package/dist/tools/SearchGitHubCode.js +4 -3
- package/package.json +7 -5
- package/dist/prompts/GenerateTerraformConfiguration.js +0 -29
- package/dist/utils/__tests__/logger.test.js +0 -21
- package/dist/utils/logger.js +0 -9
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ The GH PAT authentication is done via a prompt, so you will be asked to enter it
|
|
|
38
38
|
{
|
|
39
39
|
"servers": {
|
|
40
40
|
"dx-docs": {
|
|
41
|
-
"url": "https://api.
|
|
41
|
+
"url": "https://api.dx.pagopa.it/mcp",
|
|
42
42
|
"type": "http",
|
|
43
43
|
"headers": {
|
|
44
44
|
"x-gh-pat": "${input:github_mcp_pat}"
|
|
@@ -66,7 +66,7 @@ You need to configure it in the repository settings. See [GitHub Copilot MCP doc
|
|
|
66
66
|
{
|
|
67
67
|
"mcpServers": {
|
|
68
68
|
"pagopa-dx": {
|
|
69
|
-
"url": "https://api.
|
|
69
|
+
"url": "https://api.dx.pagopa.it/mcp",
|
|
70
70
|
"type": "http",
|
|
71
71
|
"tools": ["*"],
|
|
72
72
|
"headers": {
|
|
@@ -89,7 +89,7 @@ Follow the guided wizard to start using the DX MCP server:
|
|
|
89
89
|
|
|
90
90
|
1. **Server Name**: `dx-docs`
|
|
91
91
|
2. **Server Type**: `2` (HTTP)
|
|
92
|
-
3. **URL**: `https://api.
|
|
92
|
+
3. **URL**: `https://api.dx.pagopa.it/mcp`
|
|
93
93
|
4. **HTTP Headers**: `{"x-gh-pat": "<your-gh-PAT>"}`
|
|
94
94
|
5. **Tools**: `*` (leave as is)
|
|
95
95
|
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
1
2
|
import { Octokit } from "@octokit/rest";
|
|
2
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { logger } from "../../utils/logger.js";
|
|
4
4
|
import * as githubAuth from "../github.js";
|
|
5
5
|
vi.mock("@octokit/rest");
|
|
6
6
|
describe("verifyGithubUser", () => {
|
|
7
|
+
let loggerSpy;
|
|
7
8
|
beforeEach(() => {
|
|
8
9
|
vi.clearAllMocks();
|
|
9
10
|
process.env.REQUIRED_ORGANIZATIONS = "pagopa";
|
|
11
|
+
// Get logger and spy on its methods - no need for special configuration
|
|
12
|
+
const logger = getLogger(["mcpserver", "github-auth"]);
|
|
13
|
+
loggerSpy = {
|
|
14
|
+
debug: vi.spyOn(logger, "debug"),
|
|
15
|
+
error: vi.spyOn(logger, "error"),
|
|
16
|
+
warn: vi.spyOn(logger, "warn"),
|
|
17
|
+
};
|
|
10
18
|
});
|
|
11
19
|
it("returns false if no token is provided", async () => {
|
|
12
20
|
const result = await githubAuth.verifyGithubUser("");
|
|
@@ -22,10 +30,9 @@ describe("verifyGithubUser", () => {
|
|
|
22
30
|
},
|
|
23
31
|
},
|
|
24
32
|
}));
|
|
25
|
-
const errorLog = vi.spyOn(logger, "error");
|
|
26
33
|
const result = await githubAuth.verifyGithubUser("token");
|
|
27
34
|
expect(result).toBe(false);
|
|
28
|
-
expect(
|
|
35
|
+
expect(loggerSpy.error).toHaveBeenCalledWith("Error verifying GitHub organization membership", { error: expect.any(Error) });
|
|
29
36
|
});
|
|
30
37
|
it("returns false if user is not member of required org", async () => {
|
|
31
38
|
vi.mocked(Octokit).mockImplementation(() => ({
|
|
@@ -39,6 +46,7 @@ describe("verifyGithubUser", () => {
|
|
|
39
46
|
}));
|
|
40
47
|
const result = await githubAuth.verifyGithubUser("token");
|
|
41
48
|
expect(result).toBe(false);
|
|
49
|
+
expect(loggerSpy.warn).toHaveBeenCalledWith("User is not a member of any of the required organizations: pagopa");
|
|
42
50
|
});
|
|
43
51
|
it("returns true if user is member of required org", async () => {
|
|
44
52
|
vi.mocked(Octokit).mockImplementation(() => ({
|
|
@@ -52,5 +60,6 @@ describe("verifyGithubUser", () => {
|
|
|
52
60
|
}));
|
|
53
61
|
const result = await githubAuth.verifyGithubUser("token");
|
|
54
62
|
expect(result).toBe(true);
|
|
63
|
+
expect(loggerSpy.debug).toHaveBeenCalledWith("User is a member of one of the required organizations: pagopa");
|
|
55
64
|
});
|
|
56
65
|
});
|
package/dist/auth/github.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
1
2
|
import { Octokit } from "@octokit/rest";
|
|
2
3
|
import { z } from "zod/v4";
|
|
3
|
-
import { logger } from "../utils/logger.js";
|
|
4
4
|
const organizationsSchema = z
|
|
5
5
|
.array(z.string().nonempty())
|
|
6
6
|
.nonempty()
|
|
@@ -14,6 +14,7 @@ const REQUIRED_ORGANIZATIONS = organizationsSchema.parse(process.env.REQUIRED_OR
|
|
|
14
14
|
* @returns A boolean indicating whether the user is a member of a required organization.
|
|
15
15
|
*/
|
|
16
16
|
export async function verifyGithubUser(token) {
|
|
17
|
+
const logger = getLogger(["mcpserver", "github-auth"]);
|
|
17
18
|
if (!token) {
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
@@ -23,7 +24,7 @@ export async function verifyGithubUser(token) {
|
|
|
23
24
|
const { data: organizations } = await octokit.rest.orgs.listForAuthenticatedUser();
|
|
24
25
|
const isMember = organizations.some((org) => REQUIRED_ORGANIZATIONS.includes(org.login));
|
|
25
26
|
if (isMember) {
|
|
26
|
-
logger.
|
|
27
|
+
logger.debug(`User is a member of one of the required organizations: ${REQUIRED_ORGANIZATIONS.join(", ")}`);
|
|
27
28
|
}
|
|
28
29
|
else {
|
|
29
30
|
logger.warn(`User is not a member of any of the required organizations: ${REQUIRED_ORGANIZATIONS.join(", ")}`);
|
|
@@ -31,7 +32,7 @@ export async function verifyGithubUser(token) {
|
|
|
31
32
|
return isMember;
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
|
-
logger.error(
|
|
35
|
+
logger.error("Error verifying GitHub organization membership", { error });
|
|
35
36
|
return false;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it
|
|
2
|
-
vi.mock("../../utils/logger", () => ({
|
|
3
|
-
logger: {
|
|
4
|
-
error: vi.fn(),
|
|
5
|
-
info: vi.fn(),
|
|
6
|
-
warn: vi.fn(),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
9
2
|
import * as awsConfig from "../aws.js";
|
|
10
3
|
describe("aws config", () => {
|
|
11
4
|
it("should export kbRerankingEnabled as boolean", () => {
|
package/dist/config/aws.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
3
|
+
const logger = getLogger(["mcpserver", "aws-config"]);
|
|
3
4
|
// When true, enables reranking for the Bedrock knowledge base queries.
|
|
4
5
|
export const kbRerankingEnabled = (process.env.BEDROCK_KB_RERANKING_ENABLED || "true").trim().toLowerCase() ===
|
|
5
6
|
"true";
|
|
6
7
|
export const knowledgeBaseId = process.env.BEDROCK_KNOWLEDGE_BASE_ID || "";
|
|
7
|
-
logger.
|
|
8
|
+
logger.debug(`Default reranking enabled: ${kbRerankingEnabled} (from BEDROCK_KB_RERANKING_ENABLED)`);
|
|
8
9
|
export const region = process.env.AWS_REGION || "eu-central-1";
|
|
9
10
|
// List of AWS regions that support reranking
|
|
10
11
|
export const rerankingSupportedRegions = [
|
|
@@ -20,7 +21,7 @@ try {
|
|
|
20
21
|
kbRuntimeClient = new BedrockAgentRuntimeClient({ region });
|
|
21
22
|
}
|
|
22
23
|
catch (e) {
|
|
23
|
-
logger.error(
|
|
24
|
+
logger.error("Error getting bedrock agent client", { error: e });
|
|
24
25
|
process.exit(1);
|
|
25
26
|
}
|
|
26
27
|
export { kbRuntimeClient };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogTape logging configuration for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Configures logging for both the mcpserver and mcp-prompts packages.
|
|
5
|
+
* The console sink works perfectly in all environments:
|
|
6
|
+
* - AWS Lambda: stdout → CloudWatch
|
|
7
|
+
* - Local: stdout → console
|
|
8
|
+
*
|
|
9
|
+
* Log level is controlled via the LOG_LEVEL environment variable.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* Instead of importing a global logger, use getLogger directly in each module:
|
|
13
|
+
*
|
|
14
|
+
* import { getLogger } from "@logtape/logtape";
|
|
15
|
+
* const logger = getLogger(["mcpserver", "module-name"]);
|
|
16
|
+
* logger.info("Message");
|
|
17
|
+
*/
|
|
18
|
+
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
/**
|
|
21
|
+
* Zod schema for validating log levels.
|
|
22
|
+
* Based on LogTape's LogLevel type.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_LOG_LEVEL = "info";
|
|
25
|
+
const logLevelSchema = z
|
|
26
|
+
.enum(["debug", "info", "warning", "error"])
|
|
27
|
+
.catch(DEFAULT_LOG_LEVEL);
|
|
28
|
+
export async function configureLogging() {
|
|
29
|
+
const logLevel = logLevelSchema.parse(process.env.LOG_LEVEL);
|
|
30
|
+
if (logLevel !== process.env.LOG_LEVEL) {
|
|
31
|
+
// Use console.warn for this early logging before LogTape is configured
|
|
32
|
+
console.warn(`Invalid log level: ${process.env.LOG_LEVEL}. Using ${DEFAULT_LOG_LEVEL}`);
|
|
33
|
+
}
|
|
34
|
+
await configure({
|
|
35
|
+
loggers: [
|
|
36
|
+
{
|
|
37
|
+
category: ["mcpserver"],
|
|
38
|
+
lowestLevel: logLevel,
|
|
39
|
+
sinks: ["console"],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
category: ["mcp-prompts"],
|
|
43
|
+
lowestLevel: logLevel,
|
|
44
|
+
sinks: ["console"],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
category: ["logtape", "meta"],
|
|
48
|
+
lowestLevel: "warning",
|
|
49
|
+
sinks: ["console"],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
sinks: {
|
|
53
|
+
console: getConsoleSink(),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure Application Insights monitoring configuration for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* This configuration works in any environment (AWS Lambda, Azure Functions, local, etc.)
|
|
5
|
+
* and sends telemetry data to Azure Application Insights.
|
|
6
|
+
*
|
|
7
|
+
* Required environment variable:
|
|
8
|
+
* - APPLICATIONINSIGHTS_CONNECTION_STRING: The connection string for Azure Application Insights
|
|
9
|
+
*
|
|
10
|
+
* Optional environment variable:
|
|
11
|
+
* - APPINSIGHTS_SAMPLING_PERCENTAGE: Sampling percentage (0-100), defaults to 5
|
|
12
|
+
*/
|
|
13
|
+
import { getLogger } from "@logtape/logtape";
|
|
14
|
+
import { initAzureMonitor } from "@pagopa/azure-tracing/azure-monitor";
|
|
15
|
+
const logger = getLogger(["mcpserver", "monitoring"]);
|
|
16
|
+
export function configureAzureMonitoring() {
|
|
17
|
+
try {
|
|
18
|
+
// Initialize Azure Monitor using environment variables
|
|
19
|
+
// This will read APPLICATIONINSIGHTS_CONNECTION_STRING and APPINSIGHTS_SAMPLING_PERCENTAGE
|
|
20
|
+
initAzureMonitor();
|
|
21
|
+
logger.info("Azure Application Insights monitoring configured successfully");
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
logger.warn(`Failed to configure Azure monitoring: ${error instanceof Error ? error.message : String(error)}. Custom events will not be sent to Application Insights.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
|
+
import { emitCustomEvent } from "@pagopa/azure-tracing/logger";
|
|
3
|
+
const logger = getLogger(["mcpserver", "prompt-logging"]);
|
|
4
|
+
/**
|
|
5
|
+
* Decorator that adds logging to prompt load functions.
|
|
6
|
+
* Logs when a prompt is requested to both console and Azure Application Insights.
|
|
7
|
+
*/
|
|
8
|
+
export function withPromptLogging(prompt, catalogId) {
|
|
9
|
+
const originalLoad = prompt.load;
|
|
10
|
+
return {
|
|
11
|
+
...prompt,
|
|
12
|
+
load: async (args) => {
|
|
13
|
+
const eventData = {
|
|
14
|
+
arguments: JSON.stringify(args),
|
|
15
|
+
promptId: catalogId,
|
|
16
|
+
promptName: prompt.name,
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
// Log to console (goes to CloudWatch in Lambda)
|
|
20
|
+
logger.debug(`Prompt requested: ${prompt.name} - ${JSON.stringify(eventData)}`);
|
|
21
|
+
// Emit custom event to Azure Application Insights
|
|
22
|
+
emitCustomEvent("PromptRequested", {
|
|
23
|
+
arguments: JSON.stringify(args),
|
|
24
|
+
promptId: catalogId,
|
|
25
|
+
promptName: prompt.name,
|
|
26
|
+
})("mcpserver");
|
|
27
|
+
// Call the original load function and return the result
|
|
28
|
+
return await originalLoad(args);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
|
+
import { emitCustomEvent } from "@pagopa/azure-tracing/logger";
|
|
3
|
+
const logger = getLogger(["mcpserver", "tool-logging"]);
|
|
4
|
+
/**
|
|
5
|
+
* Decorator that adds logging to tool execute functions.
|
|
6
|
+
* Logs when a tool is executed to both console and Azure Application Insights.
|
|
7
|
+
* Preserves the exact original function signature and type.
|
|
8
|
+
*/
|
|
9
|
+
export function withToolLogging(tool) {
|
|
10
|
+
if (typeof tool !== "object" || !tool.execute || !tool.name) {
|
|
11
|
+
return tool;
|
|
12
|
+
}
|
|
13
|
+
const originalExecute = tool.execute;
|
|
14
|
+
const toolName = tool.name;
|
|
15
|
+
return {
|
|
16
|
+
...tool,
|
|
17
|
+
execute: async (...args) => {
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
const [toolArgs] = args;
|
|
20
|
+
const eventData = {
|
|
21
|
+
arguments: JSON.stringify(toolArgs),
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
toolName,
|
|
24
|
+
};
|
|
25
|
+
// Log to console (goes to CloudWatch in Lambda)
|
|
26
|
+
logger.debug(`Tool executed: ${toolName} - ${JSON.stringify(eventData)}`);
|
|
27
|
+
// Emit custom event to Azure Application Insights
|
|
28
|
+
emitCustomEvent("ToolExecuted", eventData)("mcpserver");
|
|
29
|
+
try {
|
|
30
|
+
// Call the original execute function and return the result
|
|
31
|
+
const result = await originalExecute(...args);
|
|
32
|
+
const executionTime = Date.now() - startTime;
|
|
33
|
+
// Log successful completion
|
|
34
|
+
logger.debug(`Tool completed: ${toolName} - execution time: ${executionTime}ms`);
|
|
35
|
+
// Emit completion event to Azure Application Insights
|
|
36
|
+
emitCustomEvent("ToolCompleted", {
|
|
37
|
+
executionTimeMs: executionTime.toString(),
|
|
38
|
+
toolName,
|
|
39
|
+
})("mcpserver");
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const executionTime = Date.now() - startTime;
|
|
44
|
+
// Log error
|
|
45
|
+
logger.error(`Tool failed: ${toolName} - ${error instanceof Error ? error.message : String(error)} - execution time: ${executionTime}ms`);
|
|
46
|
+
// Emit error event to Azure Application Insights
|
|
47
|
+
emitCustomEvent("ToolFailed", {
|
|
48
|
+
error: error instanceof Error ? error.message : String(error),
|
|
49
|
+
executionTimeMs: executionTime.toString(),
|
|
50
|
+
toolName,
|
|
51
|
+
})("mcpserver");
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
|
+
import { getEnabledPrompts } from "@pagopa/dx-mcpprompts";
|
|
1
3
|
import { FastMCP } from "fastmcp";
|
|
2
4
|
import { verifyGithubUser } from "./auth/github.js";
|
|
5
|
+
import { configureLogging } from "./config/logging.js";
|
|
6
|
+
import { configureAzureMonitoring } from "./config/monitoring.js";
|
|
3
7
|
import { serverInstructions } from "./config/server.js";
|
|
4
|
-
import {
|
|
8
|
+
import { withPromptLogging } from "./decorators/promptUsageMonitoring.js";
|
|
9
|
+
import { withToolLogging } from "./decorators/toolUsageMonitoring.js";
|
|
5
10
|
import { QueryPagoPADXDocumentationTool } from "./tools/QueryPagoPADXDocumentation.js";
|
|
6
11
|
import { SearchGitHubCodeTool } from "./tools/SearchGitHubCode.js";
|
|
7
|
-
|
|
12
|
+
// Configure logging
|
|
13
|
+
await configureLogging();
|
|
14
|
+
await configureAzureMonitoring();
|
|
15
|
+
const logger = getLogger(["mcpserver"]);
|
|
16
|
+
logger.info("MCP Server starting...");
|
|
8
17
|
// Authentication is enabled based on the AUTH_REQUIRED environment variable.
|
|
9
18
|
const server = new FastMCP({
|
|
10
19
|
authenticate: async (request) => {
|
|
@@ -31,9 +40,18 @@ const server = new FastMCP({
|
|
|
31
40
|
version: "0.0.0",
|
|
32
41
|
});
|
|
33
42
|
logger.debug(`Server instructions: \n\n${serverInstructions}`);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
logger.debug(`Loading enabled prompts...`);
|
|
44
|
+
getEnabledPrompts().then((prompts) => {
|
|
45
|
+
prompts.forEach((catalogEntry) => {
|
|
46
|
+
logger.debug(`Adding prompt: ${catalogEntry.prompt.name}`);
|
|
47
|
+
// Apply logging decorator to the prompt
|
|
48
|
+
const decoratedPrompt = withPromptLogging(catalogEntry.prompt, catalogEntry.id);
|
|
49
|
+
server.addPrompt(decoratedPrompt);
|
|
50
|
+
logger.debug(`Added prompt: ${catalogEntry.prompt.name}`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
server.addTool(withToolLogging(QueryPagoPADXDocumentationTool));
|
|
54
|
+
server.addTool(withToolLogging(SearchGitHubCodeTool));
|
|
37
55
|
// Starts the server in HTTP Stream mode.
|
|
38
56
|
server.start({
|
|
39
57
|
httpStream: {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
logger: {
|
|
4
|
-
error: vi.fn(),
|
|
5
|
-
info: vi.fn(),
|
|
6
|
-
warn: vi.fn(),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
3
|
import { queryKnowledgeBase } from "../bedrock.js";
|
|
10
4
|
describe("queryKnowledgeBase", () => {
|
|
5
|
+
let loggerSpy;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
// Get logger and spy on its methods - no need for special configuration
|
|
9
|
+
const logger = getLogger(["mcpserver", "bedrock"]);
|
|
10
|
+
loggerSpy = {
|
|
11
|
+
warn: vi.spyOn(logger, "warn"),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
11
14
|
it("should skip images in results", async () => {
|
|
12
15
|
const mockClient = {
|
|
13
16
|
config: {
|
|
@@ -26,6 +29,7 @@ describe("queryKnowledgeBase", () => {
|
|
|
26
29
|
};
|
|
27
30
|
const result = await queryKnowledgeBase("kbId", "query", mockClient, 2, false);
|
|
28
31
|
expect(result).toContain("doc1");
|
|
32
|
+
expect(loggerSpy.warn).toHaveBeenCalledWith("Images are not supported at this time. Skipping...");
|
|
29
33
|
});
|
|
30
34
|
it("should warn if reranking is not supported in region", async () => {
|
|
31
35
|
const mockClient = {
|
|
@@ -40,5 +44,6 @@ describe("queryKnowledgeBase", () => {
|
|
|
40
44
|
};
|
|
41
45
|
const result = await queryKnowledgeBase("kbId", "query", mockClient, 2, true);
|
|
42
46
|
expect(typeof result).toBe("string");
|
|
47
|
+
expect(loggerSpy.warn).toHaveBeenCalledWith("Reranking is not supported in region unsupported-region");
|
|
43
48
|
});
|
|
44
49
|
});
|
package/dist/services/bedrock.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RetrieveCommand, } from "@aws-sdk/client-bedrock-agent-runtime";
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
2
3
|
import { rerankingSupportedRegions } from "../config/aws.js";
|
|
3
|
-
import { logger } from "../utils/logger.js";
|
|
4
4
|
/**
|
|
5
5
|
* Queries a Bedrock knowledge base with a given query, handling reranking and result serialization.
|
|
6
6
|
* This method interacts with the AWS Bedrock service to retrieve knowledge base results.
|
|
@@ -15,6 +15,7 @@ import { logger } from "../utils/logger.js";
|
|
|
15
15
|
* @returns A serialized string of the query results.
|
|
16
16
|
*/
|
|
17
17
|
export async function queryKnowledgeBase(knowledgeBaseId, query, kbAgentClient, numberOfResults = 5, reranking = false, rerankingModelName = "AMAZON") {
|
|
18
|
+
const logger = getLogger(["mcpserver", "bedrock"]);
|
|
18
19
|
const clientRegion = await kbAgentClient.config.region();
|
|
19
20
|
let rerankingEnabled = reranking;
|
|
20
21
|
// Reranking is only supported in specific AWS regions.
|
|
@@ -2,18 +2,25 @@ import { z } from "zod";
|
|
|
2
2
|
import { kbRerankingEnabled, kbRuntimeClient, knowledgeBaseId, } from "../config/aws.js";
|
|
3
3
|
import { queryKnowledgeBase } from "../services/bedrock.js";
|
|
4
4
|
/**
|
|
5
|
-
* A tool that provides access to the complete
|
|
6
|
-
* It uses a Bedrock knowledge base to answer queries about
|
|
5
|
+
* A tool that provides access to the complete PagoPA DX documentation.
|
|
6
|
+
* It uses a Bedrock knowledge base to answer queries about DX tools, patterns, and best practices.
|
|
7
7
|
*/
|
|
8
8
|
export const QueryPagoPADXDocumentationTool = {
|
|
9
9
|
annotations: {
|
|
10
|
-
title: "Query PagoPA DX
|
|
10
|
+
title: "Query PagoPA DX documentation",
|
|
11
11
|
},
|
|
12
|
-
description: `This tool provides access to the complete
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
description: `This tool provides access to the complete PagoPA DX documentation covering:
|
|
13
|
+
- Getting started, monorepo setup, dev containers, and GitHub collaboration
|
|
14
|
+
- Git workflows and pull requests
|
|
15
|
+
- DX pipelines setup and management
|
|
16
|
+
- TypeScript development (npm scripts, ESLint, code review)
|
|
17
|
+
- Terraform (folder structure, DX modules, Azure provider, pre-commit hooks, validation, deployment, drift detection)
|
|
18
|
+
- Azure development (naming conventions, policies, IAM, API Management, monitoring, networking, deployments, static websites, Service Bus, data archiving)
|
|
19
|
+
- Container development (Docker images)
|
|
20
|
+
- Contributing to DX (Azure provider, Terraform modules, documentation)
|
|
21
|
+
|
|
22
|
+
All prompts and questions should be written in English.
|
|
23
|
+
For Terraform module details (input/output variables, examples), use the \`searchModules\` tool.
|
|
17
24
|
`,
|
|
18
25
|
execute: async (args) => {
|
|
19
26
|
const result = await queryKnowledgeBase(knowledgeBaseId, args.query, kbRuntimeClient, undefined, kbRerankingEnabled);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { getLogger } from "@logtape/logtape";
|
|
1
2
|
import { Octokit } from "@octokit/rest";
|
|
2
3
|
import { z } from "zod";
|
|
3
|
-
import { logger } from "../utils/logger.js";
|
|
4
4
|
const defaultOrg = process.env.GITHUB_SEARCH_ORG || "pagopa";
|
|
5
5
|
/**
|
|
6
6
|
* A tool that searches for code in a GitHub organization.
|
|
@@ -15,6 +15,7 @@ Use this to find examples of specific code patterns, such as Terraform module us
|
|
|
15
15
|
For example, search for "pagopa-dx/azure-function-app/azurerm" to find examples of the azure-function-app module usage.
|
|
16
16
|
Returns file contents matching the search query.`,
|
|
17
17
|
execute: async (args, context) => {
|
|
18
|
+
const logger = getLogger(["mcpserver", "github-search"]);
|
|
18
19
|
const org = defaultOrg;
|
|
19
20
|
const token = context.session?.token;
|
|
20
21
|
if (!token) {
|
|
@@ -52,7 +53,7 @@ Returns file contents matching the search query.`,
|
|
|
52
53
|
return null;
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
|
-
logger.error(
|
|
56
|
+
logger.error(`Error fetching file ${item.path}`, { error });
|
|
56
57
|
return null;
|
|
57
58
|
}
|
|
58
59
|
}));
|
|
@@ -66,7 +67,7 @@ Returns file contents matching the search query.`,
|
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
catch (error) {
|
|
69
|
-
logger.error(
|
|
70
|
+
logger.error("Error searching GitHub code", { error });
|
|
70
71
|
throw error;
|
|
71
72
|
}
|
|
72
73
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagopa/dx-mcpserver",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An MCP server that support developers using DX tools.",
|
|
6
6
|
"repository": {
|
|
@@ -20,19 +20,20 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@aws-sdk/client-bedrock-agent-runtime": "^3.583.0",
|
|
23
|
+
"@logtape/logtape": "^1.1.1",
|
|
23
24
|
"@octokit/rest": "^22.0.0",
|
|
24
25
|
"axios": "^1.12.2",
|
|
25
26
|
"fastmcp": "^3.19.1",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"zod": "^3.25.76"
|
|
27
|
+
"zod": "^3.25.76",
|
|
28
|
+
"@pagopa/dx-mcpprompts": "^0.0.1",
|
|
29
|
+
"@pagopa/azure-tracing": "^0.4.8"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.16.2",
|
|
33
33
|
"@vitest/coverage-v8": "^3.2.4",
|
|
34
34
|
"eslint": "^9.30.0",
|
|
35
35
|
"prettier": "3.6.2",
|
|
36
|
+
"tsx": "^4.20.6",
|
|
36
37
|
"typescript": "~5.8.3",
|
|
37
38
|
"vitest": "^3.2.4",
|
|
38
39
|
"@pagopa/eslint-config": "^5.1.0"
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"build": "tsc",
|
|
42
43
|
"lint": "eslint --fix src",
|
|
43
44
|
"lint:check": "eslint src",
|
|
45
|
+
"start": "tsx src/index.ts",
|
|
44
46
|
"format": "prettier --write .",
|
|
45
47
|
"format:check": "prettier --check .",
|
|
46
48
|
"typecheck": "tsc --noEmit",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A prompt that generates a Terraform configuration following PagoPA DX best practices.
|
|
3
|
-
* It guides the user to query the knowledge base for information on folder structure,
|
|
4
|
-
* modules, and conventions before generating the code.
|
|
5
|
-
*/
|
|
6
|
-
export const GenerateTerraformConfigurationPrompt = {
|
|
7
|
-
// The arguments for the prompt.
|
|
8
|
-
arguments: [
|
|
9
|
-
{
|
|
10
|
-
description: "Requirements for the Terraform configuration to generate.",
|
|
11
|
-
name: "requirements",
|
|
12
|
-
required: false,
|
|
13
|
-
},
|
|
14
|
-
],
|
|
15
|
-
// The description of the prompt.
|
|
16
|
-
description: "Generates a Terraform configuration following PagoPA DX best practices.",
|
|
17
|
-
// The function that loads the prompt.
|
|
18
|
-
load: async (args) => `To generate a Terraform configuration for ${args.requirements || "any requirement"}, you must follow the PagoPA DX best practices.
|
|
19
|
-
|
|
20
|
-
You can find information about the "infrastructure folder structure", "Using DX terraform modules", "Using DX terraform provider" and other conventions by using the \`QueryPagoPADXDocumentation\` tool. For example, you can ask "what is the infrastructure folder structure?", but you must ask for the other information as well.
|
|
21
|
-
It's suggested to call the \`QueryPagoPADXDocumentation\` tool multiple times to gather all the necessary information using simple and scoped questions.
|
|
22
|
-
|
|
23
|
-
When generating the configuration, remember to:
|
|
24
|
-
1. Use existing Terraform modules from the \`pagopa-dx\` namespace whenever possible. You can search for them using the \`searchModules\` tool.
|
|
25
|
-
2. Strictly follow the correct folder structure for infrastructure resources.
|
|
26
|
-
3. Generate HCL code that is clean, readable, and well-documented.`,
|
|
27
|
-
// The name of the prompt.
|
|
28
|
-
name: "generate-terraform-configuration",
|
|
29
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
vi.mock("../logger", () => ({
|
|
3
|
-
logger: {
|
|
4
|
-
error: vi.fn(),
|
|
5
|
-
info: vi.fn(),
|
|
6
|
-
warn: vi.fn(),
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
9
|
-
import { logger } from "../logger.js";
|
|
10
|
-
describe("logger", () => {
|
|
11
|
-
it("should have info, warn, error methods", () => {
|
|
12
|
-
expect(typeof logger.info).toBe("function");
|
|
13
|
-
expect(typeof logger.warn).toBe("function");
|
|
14
|
-
expect(typeof logger.error).toBe("function");
|
|
15
|
-
});
|
|
16
|
-
it("should not throw when calling info, warn, error", () => {
|
|
17
|
-
expect(() => logger.info("test info")).not.toThrow();
|
|
18
|
-
expect(() => logger.warn("test warn")).not.toThrow();
|
|
19
|
-
expect(() => logger.error("test error")).not.toThrow();
|
|
20
|
-
});
|
|
21
|
-
});
|
package/dist/utils/logger.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import pino from "pino";
|
|
2
|
-
import { pinoLambdaDestination } from "pino-lambda";
|
|
3
|
-
// Creates a Pino logger instance configured for Lambda environments.
|
|
4
|
-
const destination = pinoLambdaDestination();
|
|
5
|
-
export const logger = pino({
|
|
6
|
-
transport: {
|
|
7
|
-
target: "pino-pretty",
|
|
8
|
-
},
|
|
9
|
-
}, destination);
|