@pagopa/dx-mcpserver 0.0.1 → 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 CHANGED
@@ -4,17 +4,6 @@
4
4
 
5
5
  This package contains the implementation of a Model Context Protocol (MCP) server.
6
6
 
7
- ## Architecture
8
-
9
- The architecture allows any Model Context Protocol (MCP) compliant client (such as GitHub Copilot) to query the [PagoPA DX technical documentation](https://dx.pagopa.it/) in natural language, receiving contextualized and up-to-date answers.
10
-
11
- 1. **Content Upload**: On each release of the documentation website, Markdown and text files (`.md`, `.txt`) are uploaded to an S3 bucket.
12
- 2. **Indexing**: From there, the documents are processed by **Amazon Bedrock Knowledge Bases**, which handles the embedding and semantic indexing process.
13
- 3. **Vector Storage**: The resulting embeddings are saved in a Vector Bucket (an S3-based vector database), enabling efficient and persistent semantic search.
14
- 4. **Query and Retrieval**: When an MCP client sends a query, an **AWS Lambda** function implementing the MCP Server queries the Knowledge Base to retrieve the most relevant content and returns the response to the client.
15
-
16
- This approach allows AI agents like Copilot to access the documentation context in a structured way, keeping the orchestration, storage, and semantic retrieval layers separate.
17
-
18
7
  ## Features
19
8
 
20
9
  The server currently exposes the following capabilities:
@@ -25,35 +14,50 @@ The server currently exposes the following capabilities:
25
14
  - **Prompts**:
26
15
  - `GenerateTerraformConfiguration`: Guides the generation of Terraform configurations following PagoPA DX best practices.
27
16
 
28
- ## How to use it
17
+ ## Authentication
18
+
19
+ The server requires a fine-grained [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens) for authentication with the following settings:
20
+
21
+ - **Resource owner:**
22
+ - Choose the **pagopa** organization
23
+ - **Repository access:**
24
+ - Public Repositories (read-only)
25
+ - **Organization permissions:**
26
+ - Members: Read-only (to verify membership in the pagopa organization)
27
+
28
+ ## Usage
29
29
 
30
30
  This server can be used by any MCP-compliant client.
31
31
 
32
- <details>
33
- <summary><b>VS Code</b></summary>
32
+ ### VS Code
34
33
 
35
34
  Update your configuration file with the following. See [VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more info.
36
-
37
- #### VS Code Remote Server Connection
35
+ The GH PAT authentication is done via a prompt, so you will be asked to enter it the first time you use the server.
38
36
 
39
37
  ```json
40
38
  {
41
39
  "servers": {
42
40
  "dx-docs": {
43
- "url": "https://api.dev.dx.pagopa.it/mcp",
41
+ "url": "https://api.dx.pagopa.it/mcp",
44
42
  "type": "http",
45
43
  "headers": {
46
- "x-gh-pat": "${env:GH_PAT}"
44
+ "x-gh-pat": "${input:github_mcp_pat}"
47
45
  }
48
- }
46
+ },
47
+ "inputs": [
48
+ {
49
+ "type": "promptString",
50
+ "id": "github_mcp_pat",
51
+ "description": "GitHub Personal Access Token",
52
+ "password": true
53
+ }
54
+ ]
49
55
  }
50
56
  }
51
57
  ```
52
58
 
53
- </details>
59
+ ### GitHub Copilot Coding Agent
54
60
 
55
- <details>
56
- <summary><b>GitHub Copilot Coding Agent</b></summary>
57
61
  You need to configure it in the repository settings. See [GitHub Copilot MCP docs](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp) for more info.
58
62
 
59
63
  1. **Declare the MCP Server**: In the "Copilot" >> "Coding agent" panel of your repository settings, add an MCP Server declaration as follows:
@@ -62,7 +66,7 @@ You need to configure it in the repository settings. See [GitHub Copilot MCP doc
62
66
  {
63
67
  "mcpServers": {
64
68
  "pagopa-dx": {
65
- "url": "https://api.dev.dx.pagopa.it/mcp",
69
+ "url": "https://api.dx.pagopa.it/mcp",
66
70
  "type": "http",
67
71
  "tools": ["*"],
68
72
  "headers": {
@@ -77,7 +81,19 @@ You need to configure it in the repository settings. See [GitHub Copilot MCP doc
77
81
 
78
82
  Once configured, Copilot can autonomously invoke the MCP server's tools during task execution, using it to access documentation context and improve the quality of its code generation.
79
83
 
80
- </details>
84
+ ### GitHub Copilot CLI
85
+
86
+ To use the MCP server with [GitHub Copilot CLI](https://github.com/features/copilot/cli/), run the cli with `copilot` and prompt `/mcp add` to start the configuration of the MCP server
87
+
88
+ Follow the guided wizard to start using the DX MCP server:
89
+
90
+ 1. **Server Name**: `dx-docs`
91
+ 2. **Server Type**: `2` (HTTP)
92
+ 3. **URL**: `https://api.dx.pagopa.it/mcp`
93
+ 4. **HTTP Headers**: `{"x-gh-pat": "<your-gh-PAT>"}`
94
+ 5. **Tools**: `*` (leave as is)
95
+
96
+ Use `Tab` to navigate between fields and `Ctrl+S` to save.
81
97
 
82
98
  ## Development
83
99
 
@@ -102,3 +118,14 @@ To build the Docker container for this application, run the following command fr
102
118
  ```bash
103
119
  docker build -t dx/mcp-server -f ./apps/mcpserver/Dockerfile .
104
120
  ```
121
+
122
+ ## Architecture
123
+
124
+ The architecture allows any Model Context Protocol (MCP) compliant client (such as GitHub Copilot) to query the [PagoPA DX technical documentation](https://dx.pagopa.it/) in natural language, receiving contextualized and up-to-date answers.
125
+
126
+ 1. **Content Upload**: On each release of the documentation website, Markdown and text files (`.md`, `.txt`) are uploaded to an S3 bucket.
127
+ 2. **Indexing**: From there, the documents are processed by **Amazon Bedrock Knowledge Bases**, which handles the embedding and semantic indexing process.
128
+ 3. **Vector Storage**: The resulting embeddings are saved in a Vector Bucket (an S3-based vector database), enabling efficient and persistent semantic search.
129
+ 4. **Query and Retrieval**: When an MCP client sends a query, an **AWS Lambda** function implementing the MCP Server queries the Knowledge Base to retrieve the most relevant content and returns the response to the client.
130
+
131
+ This approach allows AI agents like Copilot to access the documentation context in a structured way, keeping the orchestration, storage, and semantic retrieval layers separate.
@@ -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(errorLog).toHaveBeenCalledWith(expect.any(Error), "Error verifying GitHub organization membership:");
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
  });
@@ -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.info(`User is a member of one of the required organizations: ${REQUIRED_ORGANIZATIONS.join(", ")}`);
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(error, "Error verifying GitHub organization membership:");
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, vi } from "vitest";
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", () => {
@@ -1,10 +1,11 @@
1
1
  import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime";
2
- import { logger } from "../utils/logger.js";
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.info(`Default reranking enabled: ${kbRerankingEnabled} (from BEDROCK_KB_RERANKING_ENABLED)`);
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(e, "Error getting bedrock agent client");
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 { GenerateTerraformConfigurationPrompt } from "./prompts/GenerateTerraformConfiguration.js";
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
- import { logger } from "./utils/logger.js";
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
- server.addPrompt(GenerateTerraformConfigurationPrompt);
35
- server.addTool(QueryPagoPADXDocumentationTool);
36
- server.addTool(SearchGitHubCodeTool);
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 { describe, expect, it, vi } from "vitest";
2
- vi.mock("../../utils/logger", () => ({
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
  });
@@ -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 Terraform documentation for PagoPA Dx.
6
- * It uses a Bedrock knowledge base to answer queries about Terraform modules and best practices.
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 Terraform documentation",
10
+ title: "Query PagoPA DX documentation",
11
11
  },
12
- description: `This tool provides access to the complete Terraform documentation for PagoPA Dx.
13
- Use this knowledge base to generate or review Terraform configurations aligned with the official PagoPA Dx module conventions.
14
- All prompts and questions should be written in English, so that the tool responds using English resource and variable names.
15
- The tool should be used to explain, guide, or suggest Terraform usage based on verified module documentation and internal best practices.
16
- Use only modules from the pagopa-dx namespace. To get terraform modules descriptions, input/output variables and examples, use the \`searchModules\` tool.
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(error, `Error fetching file ${item.path}`);
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(error, "Error searching GitHub code");
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.1",
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,18 +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
- "pino": "^9.1.0",
27
- "pino-lambda": "^4.4.1",
28
- "pino-pretty": "^13.1.1",
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
+ "prettier": "3.6.2",
36
+ "tsx": "^4.20.6",
35
37
  "typescript": "~5.8.3",
36
38
  "vitest": "^3.2.4",
37
39
  "@pagopa/eslint-config": "^5.1.0"
@@ -40,6 +42,7 @@
40
42
  "build": "tsc",
41
43
  "lint": "eslint --fix src",
42
44
  "lint:check": "eslint src",
45
+ "start": "tsx src/index.ts",
43
46
  "format": "prettier --write .",
44
47
  "format:check": "prettier --check .",
45
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
- });
@@ -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);