Skip to content

hkevin01/mcp-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MCP Server Scaffolding

Production-oriented Model Context Protocol server scaffolding for TypeScript. This repository gives you a working starting point for building MCP servers that need more than a bare transport layer. It already includes transport bootstrapping, tool registration, schema validation, execution timeouts, context resolution, plugin hooks, logging, authentication hooks, and a test harness.

The goal of this project is not only to expose tools to an MCP client, but to do it in a way that remains understandable when the server grows. A real MCP server usually has to answer three questions at once: what tools exist, what inputs are valid, and what context should be injected before those tools run. This scaffold separates those concerns so new tools can be added without reworking the runtime.

Important

This repository is a scaffold, not a finished product. The runtime, transports, schemas, and tool registry are real, but the service clients are intentionally lightweight examples that you are expected to adapt to your own systems and trust boundaries.

Table Of Contents

What This Project Is

This repository is a TypeScript MCP server that can run over either Streamable HTTP or stdio. The runtime is created in src/server.ts, started from src/index.ts, and configured through environment variables read by src/config/serverConfig.ts. The server builds a set of tool definitions, wraps them in a registry, validates inputs and outputs with JSON schema, and then exposes them to an MCP client through the official @modelcontextprotocol/sdk.

In practice, that means you can use this repository as a base when you need a server that supports structured tool inputs, context-aware execution, integration stubs, and predictable operational behavior. It is useful when you want to avoid writing the same transport, auth, validation, and logging code for every new MCP service.

Why This Scaffold Exists

Small MCP demos are easy to write, but they often become difficult to maintain once you introduce multiple tools, external services, and different execution environments. This scaffold exists to solve that exact transition. It separates the transport layer from the tool layer, keeps schemas explicit, and gives each request a consistent execution context so that authentication, mission state, and logging can be handled in one place.

That structure matters because MCP servers usually sit between language models and operational systems. When a model is allowed to call a tool, you need to know what the tool does, how the arguments are validated, how long it is allowed to run, and how errors are surfaced back to the client. Those concerns are already wired here.

Note

The scaffold defaults to HTTP transport when no environment override is present. The integration tests also assert that behavior, which makes the default visible in both the code and the test suite.

Feature Snapshot

# Area What It Does Why It Matters
1 Transport modes Runs over Streamable HTTP or stdio using the official MCP SDK. Lets the same server support local development flows and remotely hosted clients.
2 Tool registry Registers tool definitions once and executes them through a shared registry. Keeps validation, plugins, and timeout handling consistent across every tool.
3 Schema validation Validates tool inputs and optional outputs against JSON schema. Prevents malformed requests from reaching tool logic and improves client interoperability.
4 Context providers Builds system, user, and mission context for each execution. Gives tools the runtime facts they need without hard-coding environment state into every handler.
5 Auth hooks Supports API-key checks for HTTP requests and scope-aware execution metadata. Creates a clear place to replace development auth with production identity controls.
6 Plugin lifecycle Offers request start, success, and error hooks around tool execution. Makes auditing, metrics, and policy enforcement easier to add later.
7 Timeout wrapper Stops long-running tool execution after a configured limit. Protects the server from stuck dependencies and preserves client responsiveness.
8 Testing Includes Vitest coverage for context, tool registration, and integration behavior. Gives you a starting verification layer before you add domain-specific code.

Note: This table summarizes the runtime capabilities that already exist in the repository today, not aspirational roadmap items.

Tech Stack

The stack is intentionally narrow. Each dependency supports a specific part of the server lifecycle, and there is very little framework overhead beyond the MCP SDK itself.

# Dependency Role In This Repository Why It Was Chosen
1 TypeScript Implements the runtime, tool contracts, and context types. Provides type safety across request handling, service wiring, and tool execution.
2 @modelcontextprotocol/sdk Supplies the MCP server primitives, stdio transport, and Streamable HTTP transport. It is the canonical SDK for implementing standards-aligned MCP servers.
3 Express Hosts the HTTP transport endpoints and health route. It keeps the HTTP side simple and readable without hiding request flow.
4 Ajv Validates JSON schemas for tool input and output envelopes. It is fast, mature, and well suited for schema-first interfaces.
5 Zod Defines runtime input shapes used when registering tools with the MCP SDK. It keeps the tool registration surface ergonomic while JSON schema remains authoritative for validation.
6 dotenv Loads environment variables during startup. It makes local configuration predictable without custom bootstrapping code.
7 Vitest Runs unit and integration tests. It is fast enough for iterative work and straightforward for TypeScript projects.
8 tsx Runs TypeScript directly during development. It shortens the edit-run loop and avoids a manual build step while iterating.

Note: This table explains the current production and development dependencies declared in package.json and how they map to the repository structure.

Architecture

The runtime is centered around a single composition path. Configuration is loaded first, service clients are created from that configuration, tool definitions are built from those services, and a ToolRegistry executes them behind a uniform validation and plugin boundary. The transport layer sits outside that registry so HTTP and stdio can share the same execution model.

flowchart TD
    A[Client] --> B{Transport}
    B -->|HTTP| C[Express + Streamable HTTP]
    B -->|stdio| D[Stdio Transport]
    C --> E[createMcpInstance]
    D --> E
    E --> F[ToolRegistry]
    F --> G[Schema Validation]
    G --> H[Plugin Hooks]
    H --> I[Execution Context]
    I --> J[Tool Handler]
    J --> K[Service Clients]
    J --> L[Structured Result]
Loading

Note: This diagram shows the execution path from an MCP client to a tool result, including the layers that enforce validation and shared runtime behavior.

The most important architectural decision in this codebase is that tool code does not own transport setup, authentication parsing, or context assembly. Those concerns are handled in the runtime before a tool executes. That keeps tool handlers focused on domain work instead of protocol plumbing.

Tip

If you plan to add many tools, keep following the current pattern: define schema first, create a thin tool wrapper second, and isolate remote API or database logic inside src/services/. That separation will scale much better than embedding service calls directly inside the transport layer.

Repository Layout

The directory structure is organized around responsibilities rather than technology layers alone. Tools live separately from services because tools define the contract that clients see, while services handle the implementation details of talking to other systems. Context providers are also isolated because context is cross-cutting and should not be rebuilt independently inside every handler.

# Path Purpose Why It Exists
1 src/index.ts Starts the runtime and exits cleanly on fatal startup errors. Keeps process entry concerns separate from server composition.
2 src/server.ts Builds services, registry, transports, and runtime startup flow. Acts as the core composition root for the application.
3 src/config/ Loads and normalizes environment-driven server configuration. Prevents configuration parsing from leaking into business logic.
4 src/context/ Resolves system, user, and mission context objects. Gives tool execution a consistent context envelope.
5 src/tools/ Defines MCP-visible tools and their input shapes. Creates a stable contract for model-facing interactions.
6 src/services/ Implements integration-facing client logic and logging. Separates external system access from tool orchestration.
7 src/plugins/ Provides lifecycle extension points around execution. Lets you add observability or policy hooks without changing every tool.
8 src/schemas/ Stores JSON schema documents for tool requests and shared types. Keeps tool contracts explicit and reusable.
9 src/utils/ Holds auth, validation, timeout, and error helpers. Centralizes infrastructure behavior that multiple slices depend on.
10 tests/ Verifies context behavior, tool registration, and integration defaults. Documents expected behavior while protecting against regressions.

Note: This table is a high-level guide to where responsibilities live so new contributors can add features without guessing where code belongs.

Expanded folder tree
mcp-server/
├── package.json
├── README.md
├── tsconfig.json
├── src/
│   ├── index.ts
│   ├── server.ts
│   ├── config/
│   │   └── serverConfig.ts
│   ├── context/
│   │   ├── missionContext.ts
│   │   ├── systemContext.ts
│   │   └── userContext.ts
│   ├── plugins/
│   │   └── index.ts
│   ├── schemas/
│   │   ├── assuranceSchema.json
│   │   ├── commonTypes.json
│   │   ├── customToolSchema.json
│   │   ├── jiraSchema.json
│   │   └── searchSchema.json
│   ├── services/
│   │   ├── dbClient.ts
│   │   ├── jiraClient.ts
│   │   ├── loggingService.ts
│   │   └── nasaAssuranceClient.ts
│   ├── tools/
│   │   ├── customTool.ts
│   │   ├── index.ts
│   │   ├── jiraTool.ts
│   │   ├── nasaAssuranceTool.ts
│   │   └── searchTool.ts
│   ├── types/
│   │   ├── ContextTypes.ts
│   │   └── ToolTypes.ts
│   └── utils/
│       ├── auth.ts
│       ├── errorHandler.ts
│       └── schemaValidator.ts
└── tests/
    ├── contextTests.test.ts
    ├── integration.test.ts
    └── toolTests.test.ts

How The Runtime Works

At startup, createRuntime() loads normalized configuration, creates service clients, loads built-in plugins, and builds the tool registry. From there, the runtime selects HTTP or stdio mode based on MCP_TRANSPORT. The result is one composition path with two transport adapters instead of two different servers that happen to expose similar tools.

When a request reaches a tool, the runtime authenticates it, resolves context, validates the input schema, runs plugin hooks, executes the tool with a timeout, optionally validates the output schema, and formats the result for the MCP client. This is the core reason the scaffold is useful: every tool benefits from the same safety and observability envelope.

sequenceDiagram
    participant Client
    participant Transport
    participant Runtime
    participant Registry
    participant Tool
    participant Service

    Client->>Transport: MCP request
    Transport->>Runtime: Forward request
    Runtime->>Runtime: Authenticate and build execution context
    Runtime->>Registry: Execute tool(name, args, context)
    Registry->>Registry: Validate input schema
    Registry->>Tool: Run handler with timeout
    Tool->>Service: Query dependency or perform action
    Service-->>Tool: Return data
    Tool-->>Registry: Structured result
    Registry->>Registry: Validate output schema when configured
    Registry-->>Client: Summary + structuredContent
Loading

Note: This sequence diagram shows the control flow that every tool invocation shares, regardless of which transport receives the request.

# Runtime Stage What Happens Operational Benefit
1 Configuration load Environment variables are parsed into a typed config object. Startup behavior stays deterministic across environments.
2 Service creation Logger and integration clients are constructed once. Prevents scattered initialization logic.
3 Tool definition assembly Tools are created from shared services. Encourages consistent dependency injection.
4 Request authentication Headers and scopes are evaluated before execution. Rejects unauthorized traffic early.
5 Context resolution System, user, and mission facts are injected into the execution context. Gives tools richer information without manual plumbing.
6 Validation and timeout Input is validated and execution is bounded by time. Improves safety and resilience under bad inputs or slow dependencies.
7 Lifecycle hooks Plugins observe success and failure paths. Supports auditing and metrics without rewriting handlers.
8 Structured response The MCP client receives human-readable text and machine-readable content. Helps both interactive use and downstream automation.

Note: This table breaks the runtime into concrete stages so it is easier to reason about where to extend behavior and where to debug failures.

Getting Started

You can run the server locally in either HTTP mode or stdio mode. HTTP is the default and is useful when you want a network-accessible MCP endpoint. stdio is useful when another process spawns this server directly and communicates over standard input and output.

npm install
npm run dev:http

That starts the server in HTTP mode using tsx for rapid iteration. The HTTP runtime exposes a health endpoint and the MCP endpoint handled by the official Streamable HTTP transport.

If you want the server to behave like a local subprocess tool host instead, run:

npm run dev:stdio

Once you are ready for a production-style build artifact, compile and run the emitted JavaScript:

npm run build
npm start

Tip

Use HTTP mode during integration testing with remote clients and stdio mode when validating that a desktop client or local orchestrator can spawn the process directly.

Configuration

Configuration is environment-driven, and src/config/serverConfig.ts is the single normalization point. That design is important because environment variables are messy in raw form. The config layer turns string values into booleans, numbers, sets, and transport enums before the rest of the application reads them.

Create a local environment file or export variables in your shell before starting the server. The defaults are intentionally development-friendly, especially for transport mode and network binding.

MCP_TRANSPORT=http
MCP_HOST=127.0.0.1
MCP_PORT=8080
MCP_EXECUTION_TIMEOUT_MS=15000
MCP_LOG_LEVEL=info
# Variable Meaning Default Behavior
1 MCP_SERVER_NAME Logical server name advertised to clients. Falls back to mcp-server.
2 MCP_SERVER_VERSION Logical server version exposed by the runtime. Falls back to 0.1.0.
3 MCP_TRANSPORT Selects http or stdio mode. Defaults to HTTP unless explicitly set to stdio.
4 MCP_HOST HTTP bind address. Defaults to 127.0.0.1.
5 MCP_PORT HTTP bind port. Defaults to 8080.
6 MCP_CORS_ORIGINS Controls allowed browser origins for HTTP mode. * means any origin is allowed.
7 MCP_EXECUTION_TIMEOUT_MS Caps tool execution time in milliseconds. Defaults to 15000.
8 MCP_LOG_LEVEL Sets the logging verbosity. Defaults to info.

Note: This table covers server-level configuration that changes transport and operational behavior.

# Variable Meaning Default Behavior
1 MCP_AUTH_ENABLED Turns HTTP API-key checks on or off. Defaults to disabled.
2 MCP_API_KEY_HEADER Defines the HTTP header used for API-key lookup. Defaults to x-mcp-api-key.
3 MCP_API_KEYS Comma-separated list of valid API keys. Parses into an empty set when unset.
4 MISSION_ID Mission identifier injected into context. Defaults to mission-sample.
5 MISSION_NAME Mission display name injected into context. Defaults to Mission Control Scaffold.
6 MISSION_ENVIRONMENT Mission environment value exposed to tools. Falls back to NODE_ENV or development.
7 JIRA_BASE_URL / JIRA_TOKEN Jira integration connection settings. Remain undefined until you wire a real service.
8 NASA_ASSURANCE_BASE_URL / NASA_ASSURANCE_TOKEN NASA assurance integration connection settings. Remain undefined until you wire a real service.

Note: This table covers authentication, mission context, and external integration wiring.

Warning

The included API-key pattern is suitable as a development hook or for tightly controlled internal use, but it should not be treated as the final security model for public or internet-facing deployments. Replace it with a stronger authorization flow before exposing this server broadly.

Included Tools

The scaffold ships with four example tools so the registry, schemas, summaries, and service boundaries are visible immediately. Three are read-oriented examples and one is a write-capable placeholder. Together they demonstrate the difference between query-style tools and side-effecting actions.

# Tool Name What It Does Scope Pattern
1 jira_query Searches Jira issues by query text with optional project and limit filters. Requires tools:read.
2 nasa_assurance_lookup Retrieves requirement assurance status and optional evidence details. Requires tools:read.
3 search_knowledge Searches indexed operational or assurance knowledge. Requires tools:read.
4 custom_action Acknowledges a domain-specific action payload and serves as the write example. Requires tools:write.

Note: This table maps directly to the tool names asserted by the integration tests, so it is a reliable snapshot of the tools currently registered by default.

{
  "query": "thermal constraint",
  "projectKey": "OPS",
  "limit": 5
}

The important detail is not the sample data itself but the contract shape. Each tool has an input schema identifier, an MCP-facing input shape, a handler, and a summary function. That combination gives the client a usable interface while keeping validation and execution behavior explicit.

Extension Model

The project is designed so that new capabilities are added as slices instead of as scattered edits. A new tool should normally involve a schema, a tool module, and optionally a service client. A new context provider should only change the context layer and runtime composition. A new plugin should hook into lifecycle events without changing the registry execution semantics.

# Extension Type Where To Change Code Typical Reason
1 New tool src/schemas/, src/tools/, and src/tools/index.ts Add a new MCP capability visible to clients.
2 New service src/services/ and createServices() in src/server.ts Connect tools to a new API, database, or internal system.
3 New context provider src/context/ and resolveContexts() in src/server.ts Inject more runtime state into tool execution.
4 New plugin src/plugins/ and createRuntime() wiring Add metrics, auditing, tracing, or policy checks.
5 New schema type src/schemas/commonTypes.json or a tool-specific schema file Reuse structured contracts across multiple tools.

Note: This table explains how to extend the scaffold without collapsing its separation between contracts, execution, and integrations.

Suggested workflow for adding a new tool
  1. Add or update the JSON schema in src/schemas/ so the contract is explicit first.
  2. Create a tool module in src/tools/ that declares name, title, scopes, input shape, and handler.
  3. If the tool talks to an external system, place that logic in src/services/ instead of the tool file.
  4. Export the tool from src/tools/index.ts so the registry can include it.
  5. Add or update tests in tests/ so the registration and behavior remain visible.

Testing

The test suite is intentionally small but useful. It verifies that the runtime comes up with the expected tool list and that HTTP remains the default transport unless configuration overrides it. That baseline matters because documentation and operational expectations often drift first around entrypoints and defaults.

npm test
# Test File What It Covers Why It Matters
1 tests/toolTests.test.ts Tool-oriented behavior. Keeps tool contracts and execution slices from regressing silently.
2 tests/contextTests.test.ts Context provider behavior. Protects the runtime facts injected into tools.
3 tests/integration.test.ts Registry composition and default transport behavior. Confirms the assembled runtime still matches its documented entry behavior.

Note: This table highlights the current testing slices so contributors know where to extend coverage when they add new runtime behavior.

Operational Guidance

When you move from scaffold to production use, focus first on integration realism and trust boundaries. The code already gives you a stable structure for validation, request handling, and lifecycle hooks, but production readiness depends on the service implementations, the authentication model, and the operational policies you add around them.

Keep the runtime contract narrow and explicit. Tool names should be stable, schemas should be versioned with care, and logs should remain structured enough to support debugging and audit needs. The registry pattern in this repository makes that discipline easier because it creates one place to enforce it.

Note

A good production evolution path is usually: replace mock-like service behavior with real integrations, tighten auth, add tracing or audit plugins, and then expand the test suite around the new failure modes introduced by those external dependencies.

Scripts

# Script Command Use Case
1 build tsc -p tsconfig.json Compile the project for a production-style run.
2 dev tsx watch src/index.ts General development entrypoint.
3 dev:http tsx watch src/index.ts Explicit HTTP development mode.
4 dev:stdio cross-env MCP_TRANSPORT=stdio tsx watch src/index.ts stdio development mode for spawned clients.
5 start node dist/index.js Run the built output.
6 test vitest run Execute the test suite once.
7 test:watch vitest Run tests in watch mode during local development.

Note: This table is derived from package.json and should stay synchronized with the actual scripts in the repository.

Final Notes

This scaffold is most valuable when you preserve its boundaries. Let the runtime own transport and execution policy, let tools own client-facing capability definitions, and let services own external integration details. That architecture is what keeps an MCP server understandable after it grows beyond a single demo tool.

If you keep that separation intact, this repository can scale from a simple local prototype to a more serious internal tool platform without forcing a full rewrite of the server core.

About

Production-oriented Model Context Protocol server scaffolding for TypeScript. This repository gives you a working starting point for building MCP servers that need more than a bare transport layer. It already includes transport bootstrapping, tool registration, schema validation, execution timeouts, context resolution, plugin hooks, logging

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors