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
8 changes: 4 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ jobs:
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v1
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.17

- name: Install dependencies
run: bun install
run: bun install --frozen-lockfile

- name: Run Lint
run: bun run lint
run: bun lint
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.2.17

- name: Set package version
env:
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Run Tests

on:
pull_request:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.17

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Run tests
run: bun test
37 changes: 19 additions & 18 deletions bun.lock

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

21 changes: 8 additions & 13 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{
extends: [
eslint.configs.recommended,
tseslint.configs.recommended,
],
rules: {
'semi': ['error', 'never'],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'comma-dangle': ['error', 'always-multiline'],
},
export default tseslint.config({
extends: [eslint.configs.recommended, tseslint.configs.recommended],
rules: {
semi: ['error', 'never'],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'comma-dangle': ['error', 'always-multiline'],
},
)
})
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"format": "prettier --write \"**/*.ts\"",
"inspect": "bunx @modelcontextprotocol/inspector node dist/index.js",
"lint": "eslint src",
"prepublishOnly": "bun run build && chmod 755 dist/index.js"
Expand All @@ -42,6 +43,7 @@
"@eslint/js": "^9.30.0",
"@types/bun": "latest",
"eslint": "^9.30.0",
"prettier": "^3.6.2",
"typescript-eslint": "^8.35.0"
},
"peerDependencies": {
Expand Down
19 changes: 19 additions & 0 deletions prettier.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Config } from 'prettier'

const config: Config = {
$schema: 'https://json.schemastore.org/prettierrc',
semi: false,
singleQuote: true,
singleAttributePerLine: true,
parser: 'typescript',
overrides: [
{
files: ['*.d.ts'],
options: {
printWidth: Infinity,
},
},
],
}

export default config
45 changes: 45 additions & 0 deletions src/clients/__tests__/nominatimClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect, test } from 'bun:test'
import packageJson from '../../../package.json' with { type: 'json' }

// Note: Due to difficulties in reliably mocking 'axios' with bun:test in this environment,
// tests requiring a mocked axios instance (and thus, true unit tests for network calls) are skipped.
// The manual mock in __mocks__/axios.ts and various mock.module strategies were attempted
// but did not consistently prevent real network calls.

// If a reliable way to mock axios is found, these tests can be re-enabled.
// For now, we rely on tests for src/tools/* which mock this client.

describe('nominatimClient', () => {
// Tests that depend on a mocked axios instance are marked as .skip
test.skip('geocodeAddress (axios dependent)', () => {
it("should call the mocked axios instance's get with correct parameters and condense output", async () => {
// Placeholder
})

it('should handle API errors', async () => {
// Placeholder
})
})

test.skip('reverseGeocode (axios dependent)', () => {
it("should call the mocked axios instance's get with correct parameters and condense output", async () => {
// Placeholder
})

it('should handle API errors', async () => {
// Placeholder
})
})

describe('User-Agent', () => {
it('should correctly form User-Agent string based on package.json', () => {
const expectedUserAgent = `GeocodingMCP github.com/geocoding-ai/mcp ${packageJson.version}`
// This test primarily verifies the construction of the USER_AGENT string itself.
// The actual header being set by axios relies on axios's internal mechanisms.
expect(expectedUserAgent).toContain(
'GeocodingMCP github.com/geocoding-ai/mcp',
)
expect(expectedUserAgent).toContain(packageJson.version)
})
})
})
7 changes: 4 additions & 3 deletions src/clients/nominatimClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios'
import type { ReverseGeocodeParams } from '../types/reverseGeocodeTypes.js'
import type { GeocodeParams } from '../types/geocodeTypes.js'
import type { ReverseGeocodeParams } from '@/types/reverseGeocodeTypes.js'
import type { GeocodeParams } from '@/types/geocodeTypes.js'
import packageJson from '../../package.json' with { type: 'json' }

const USER_AGENT = `GeocodingMCP github.com/geocoding-ai/mcp ${packageJson.version}`
Expand All @@ -12,7 +12,8 @@ const nominatimClient = axios.create({
},
})

const condenseOutput = (result: any[]) => result.map(({ licence, ...item }) => item)
const condenseOutput = (result: any[]) =>
result.map(({ licence, ...item }) => item)

export const geocodeAddress = async (params: GeocodeParams) => {
const response = await nominatimClient.get('search', { params })
Expand Down
16 changes: 8 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { registerGeocodeTool } from "./tools/geocode.js"
import { registerReverseGeocodeTool } from "./tools/reverseGeocode.js"
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { registerGeocodeTool } from '@/tools/geocode.js'
import { registerReverseGeocodeTool } from '@/tools/reverseGeocode.js'
import packageJson from '../package.json' with { type: 'json' }

const server = new McpServer({
name: "geocoding",
name: 'geocoding',
version: packageJson.version,
description: "Geocoding API",
description: 'Geocoding API',
})

registerGeocodeTool(server)
Expand All @@ -18,10 +18,10 @@ registerReverseGeocodeTool(server)
async function main() {
const transport = new StdioServerTransport()
await server.connect(transport)
console.error("Geocoding API MCP Server running on stdio")
console.info('Geocoding API MCP Server running on stdio')
}

main().catch((error) => {
console.error("Fatal error in main():", error)
console.error('Fatal error in main():', error)
process.exit(1)
})
Loading