Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .data/.gitkeep
Empty file.
Binary file removed .data/context.sqlite
Binary file not shown.
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ jobs:
- name: Run tests with coverage
run: bun run test:coverage

# - name: Prepare coverage report in markdown
# if: github.event_name == 'pull_request'
# id: coverage
# uses: fingerprintjs/action-coverage-report-md@v2
# with:
# textReportPath: './coverage.txt'
# srcBasePath: './src'

# - name: Comment coverage report on PR
# if: github.event_name == 'pull_request'
# uses: marocchino/sticky-pull-request-comment@v2
# with:
# message: ${{ steps.coverage.outputs.markdownReport }}
# header: Coverage Report

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
Expand All @@ -44,6 +59,7 @@ jobs:
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
comment: false

- name: Generate badges
if: github.ref == 'refs/heads/main'
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
node_modules
dist
coverage
coverage.txt

# AI
.claude
Expand All @@ -12,4 +13,10 @@ coverage
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.production.local

# Data
.sqlite

# Others
TODO.md
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ MCPLand is a TypeScript framework for building and managing **Model Context Prot
- [Quick Start](#quick-start)
- [Commands](#commands)
- [`mcp init`](#mcp-init)
- [`mcp new`](#mcp-new)
- [`mcp serve`](#mcp-serve)
- [`mcp link`](#mcp-link)
- [Global Options](#global-options)
Expand Down Expand Up @@ -44,13 +45,16 @@ bun install -g mcpland
# Initialize a new MCP project
mcp init

# Scaffold a new MCP (inside the project)
mcp new my-mcp

# Link with Cursor IDE (stdio mode)
mcp link cursor

# Or link with Cursor IDE (SSE mode)
mcp link cursor --sse

# Start SSE server (if using SSE mode)
# Start SSE server (required for SSE mode)
mcp serve
```

Expand Down Expand Up @@ -80,6 +84,38 @@ mcp init
- OpenAI API key
- Selection of available MCP tools from registry

### `mcp new`

Scaffold a new MCP from the [base template](https://github.com/stewones/mcpland/tree/main/src/mcps/_).

**Usage:**
```bash
mcp new [name]
```

**Arguments:**
- `name` - Optional MCP name. If omitted, it will be asked for.

**What it does:**
- Creates `src/mcps/<name>/index.ts`
- Creates an initial tool at `src/mcps/<name>/tools/<tool>/index.ts`
- Prints next steps to edit your MCP and add more tools.

**Interactive prompts:**
- MCP name (if not provided as an argument)
- MCP description
- Initial tool name (e.g., `docs`)
- Tool description

**Examples:**
```bash
mcp new # Fully interactive
mcp new my-mcp # Skips name prompt, asks for the rest
```

**Notes:**
- Run this command inside your project root (where `mcpland.json` lives). If `mcp init` created a new folder, `cd` into it first.

### `mcp serve`

Start the MCPLand SSE (Server-Sent Events) server.
Expand Down Expand Up @@ -146,6 +182,7 @@ mcp <command> --help # Show help for specific command
```bash
mcp --help # Show general help and command list
mcp init --help # Show help for init command
mcp new --help # Show help for new command
mcp serve --help # Show help for serve command
mcp link --help # Show help for link command
```
Expand Down Expand Up @@ -239,8 +276,8 @@ class YourTool extends McpTool {
- [x] Add ability to serve SSE requests
- [ ] Add ability to schedule context updates
- [ ] Add ability to link with cursor globally
- [ ] Add ability to scaffold new mcps
- [x] Add ability to scaffold new mcps

# License

MIT
MIT
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcpland",
"version": "0.2.0",
"version": "0.3.0",
"private": false,
"type": "module",
"description": "Building blocks for implementing Model Context Protocol tools.",
Expand All @@ -18,8 +18,10 @@
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage --config=vitest.config.ts",
"coverage": "bun run test:coverage && open coverage/index.html",
"coverage:ci": "bun run ./scripts/coverage.ts",
"format": "format-imports ./src --config=./import.json && format-imports ./test --config=./import.json",
"badges": "bun run ./scripts/badges.ts"
"badges": "bun run ./scripts/badges.ts",
"publish": "bun run build && cd dist && npm publish"
},
"exports": {
".": {
Expand Down
32 changes: 32 additions & 0 deletions scripts/coverage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bun

import { writeFileSync } from 'node:fs';
import path from 'node:path';

async function runCoverage(): Promise<string> {
const proc = Bun.spawn(['bun', 'run', 'test:coverage'], {
stdout: 'pipe',
stderr: 'inherit',
});

const output = await new Response(proc.stdout).text();
await proc.exited;
return output;
}

function trimToActualReport(fullOutput: string): string {
const lines = fullOutput.split(/\r?\n/);
const marker = '% Coverage report from v8';
const idx = lines.findIndex((line) => line.trimStart().startsWith(marker));
if (idx === -1) return fullOutput; // fallback: nothing to trim
const trimmed = lines.slice(idx).join('\n').split(marker).join('\n').trim();
return trimmed.endsWith('\n') ? trimmed : trimmed + '\n';
}

const root = process.cwd();
const coverageTxtPath = path.resolve(root, 'coverage.txt');

const output = await runCoverage();
const trimmed = trimToActualReport(output);
writeFileSync(coverageTxtPath, trimmed, 'utf8');
console.log(`Coverage text report written to ${coverageTxtPath}`);
14 changes: 11 additions & 3 deletions src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import pkg from '../../package.json';
import { McpLandCommand } from './command';
import { InitCommand } from './commands/init';
import { LinkCommand } from './commands/link';
import { NewCommand } from './commands/new';
import { ServeCommand } from './commands/serve';

export type McpLandCliOptions = {
Expand Down Expand Up @@ -52,7 +53,9 @@ export class McpLandCli {
console.log(line);
}
console.log('');
console.log(`Run '${program} <command> --help' for more information on a specific command.`);
console.log(
`Run '${program} <command> --help' for more information on a specific command.`
);
}

async run(argv: string[] = process.argv.slice(2)): Promise<number> {
Expand All @@ -63,7 +66,7 @@ export class McpLandCli {
}

const [cmdName, ...args] = argv;

// Handle global --help flag (only when no command is specified)
if (!cmdName && (argv.includes('--help') || argv.includes('-h'))) {
this.printGlobalHelp();
Expand All @@ -83,7 +86,11 @@ export class McpLandCli {
} catch (err) {
console.error(pc.red(`Command failed: ${String(err)}`));
// If it's a help-related error, show command help
if (String(err).includes('Unknown option') || String(err).includes('requires a value') || String(err).includes('missing')) {
if (
String(err).includes('Unknown option') ||
String(err).includes('requires a value') ||
String(err).includes('missing')
) {
console.log('');
cmd.printHelp();
}
Expand All @@ -96,6 +103,7 @@ const cli = new McpLandCli({ name: 'mcp', version: pkg.version });

cli
.addCommand(new InitCommand())
.addCommand(new NewCommand())
.addCommand(new ServeCommand())
.addCommand(new LinkCommand());

Expand Down
Loading
Loading