Skip to content
Back to skills

Mcp Server

BSecurity

Add or change an MCP tool or server on this repo's shared mcp-common scaffolding — the McpTool SPI, transport-neutral ToolDefinition/ToolResult, the three transports, path confinement, MCP_USAGE.md and the *IT tests. Use when exposing functionality over MCP, wiring a new server, or when the user says "MCP tool", "MCP server", "stdio transport", or "streamable HTTP".

  • 11 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added August 31, 2026
ai-agentsjavaspringgit

Works with

  • cli
  • mcp

Security analysis

B75/100
  • criticalAccesses sensitive system or user directories

Pro scans all 2 files and shows the line behind each finding

Scanned September 28, 2026

npx -y skills add adamw7/tools --skill mcp-server --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mcp Server?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mcp Server
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/adamw7-mcp-server/badge)](https://www.skillsdirectory.com/skills/adamw7-mcp-server)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mcp-server
description: Add or change an MCP tool or server on this repo's shared mcp-common scaffolding — the McpTool SPI, transport-neutral ToolDefinition/ToolResult, the three transports, path confinement, MCP_USAGE.md and the *IT tests. Use when exposing functionality over MCP, wiring a new server, or when the user says "MCP tool", "MCP server", "stdio transport", or "streamable HTTP".
---

# MCP Server Skill

Expose functionality over the Model Context Protocol using `mcp-common`, the
shared scaffolding behind all three servers here (`data` uniqueness,
`code/context`, `adopt`). Transport, registration and error handling are already
solved — a new tool writes none of that.

## The SPI — a tool never mentions the MCP SDK
```java
public class MyTool implements McpTool {   // Function<Map<String,Object>, ToolResult>

    @Override
    public ToolDefinition getToolDefinition() {
        return new ToolDefinition("my_tool", "What it does, for the model.",
                Map.of("type", "object",
                       "properties", Map.of("path", Map.of("type", "string")),
                       "required", List.of("path")));
    }

    @Override
    public ToolResult apply(Map<String, Object> arguments) {
        String path = ToolArguments.requiredString(arguments, "path");
        return ToolResult.success(answer);          // or ToolResult.error(message)
    }
}
```

- `ToolDefinition` (name, description, JSON-schema map) and `ToolResult`
  (`success` / `error`) are the SPI's **own transport-neutral types**;
  `AbstractMcpConfiguration` translates them into the SDK's `Tool` and
  `CallToolResult`. Do not import `io.modelcontextprotocol` from a tool.
- Parse arguments with `ToolArguments`: `requiredString`, `requiredInt`,
  `optionalString`, `optionalBoolean`, `optionalInt`, `optionalBoundedInt` —
  don't hand-roll casts out of the map.
- **Don't catch-and-wrap for the protocol's sake.** `AbstractMcpConfiguration`
  turns any thrown `RuntimeException` into an error `ToolResult` naming the
  tool, so a bad argument reaches the client as an actionable tool error. Throw
  a clear exception; return `ToolResult.error` when the failure is expected.
- **The failure path masks both channels** (`FailureMessage`): the arguments it
  logs lose their credentials, and the message the client reads loses both those
  and any absolute path, keeping only the file's name. Write exception messages
  that stay useful under that — name what was wrong with the argument, not where
  on the server's disk it resolved to.

## The server
```java
@Configuration
public class McpConfiguration extends AbstractMcpConfiguration {
    @Override protected String serverName() { return "my-server"; }
    @Override protected List<McpTool> tools() { return List.of(new MyTool()); }
}
```
That is the whole server. The base class supplies all three transports, selected
with `--transport.mode`:

| Mode | Value | Endpoint |
|---|---|---|
| stdio (default) | `stdio` | — |
| streamable HTTP | `streamable-http` | `/mcp` |
| stateless HTTP | `stateless-http` | `/mcp`, no session kept |

`TransportConfigurer.configure(args)` reads the argument through `TransportMode`,
so anything but those three values is refused at startup with a message naming
them. Write the conditions with `TransportMode.Value.*` rather than a literal:
an unmatched `havingValue` registers no transport, and outside stdio it also
leaves the web server on — a server that binds its port and serves no `/mcp`.

A `Main.java` Spring Boot entry point sits next to the configuration
(`entryPointsAreNamedMain` is an ArchUnit rule in `adopt`).

## Rules that bite
- **Confine file access.** A server that reads paths from a client must clamp
  them to a configured base directory before returning any tool — the uniqueness
  server builds an `AllowedPaths.under(baseDir)` in `tools()` and hands it to the
  tool, defaulting to the working directory, so a client cannot steer it at
  `/etc/passwd`; the context server's `PathPolicy` does the same over several
  roots. Both rest on mcp-common's `PathBoundary` (`io.github.adamw7.tools.path`),
  so symlinks are followed one way everywhere — confine through it rather than
  writing another containment check. Give the boundary to the thing being confined, never to the JVM:
  a process-wide setter confines every other source in the host too, and the
  second server to start silently moves the first one's boundary.
- **The core must not depend on its MCP adapter.** ArchUnit pins this in every
  module: the uniqueness/context/adoption logic knows nothing about `mcp`, and
  only the `mcp` package may see Spring or the scaffolding. Put the adapter in
  `…/mcp`, keep the logic outside it.
- Log through log4j2 — no `System.out`, which would corrupt the stdio transport
  anyway.

## Document and test it
- Every server has an `MCP_USAGE.md` **next to its `mcp` package**, listing each
  tool, its parameters and example calls. Update it in the same change as the
  tool — it is the file users configure their client from.
- The servers are covered by `*IT` tests over real HTTP, gated behind the
  `integration-tests` profile: `mvn -P integration-tests verify`. The profile is
  declared **per module** (`data`, `code/context`, `adopt`,
  `claude-code-enforcer`) — a new module's `*IT`s do not run until that module
  gets its own copy.
- Each server's transport tests follow one shape: an abstract fixture holding the
  client, the helpers and whatever both transports must assert, plus a
  `McpStreamableHttpIT` and a `McpStatelessHttpIT` that supply only the transport
  and the client name. Copy it for a new server, and pick a payload the
  integration runner can actually serve — `adopt`'s pair *verifies* a repository
  rather than adopting one, because no runner has a logged-in `claude`.
- Unit-test the tool directly as a function: build the argument map, assert on
  the `ToolResult` text and `isError`.

## References
- `mcp-common/.../AbstractMcpConfiguration.java` — transports and registration
- `data/.../uniqueness/mcp/` — the smallest complete example
- `docs/adr/0009-mcp-servers-on-spring-boot.md` — why Spring Boot
- The three `MCP_USAGE.md` files — the user-facing contract

Files in this skill

  • README.md1 KB
  • SKILL.md5.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.