@fhirfly-io/mcp-server 0.1.0
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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/cli.js +431 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +397 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FHIRfly.io LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @fhirfly-io/mcp-server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for connecting Claude Desktop to [FHIRfly](https://fhirfly.io) healthcare reference data APIs.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
This package lets Claude Desktop look up real healthcare reference data including:
|
|
8
|
+
|
|
9
|
+
- **NDC** - Drug products and packages (FDA)
|
|
10
|
+
- **NPI** - Healthcare provider identifiers (CMS)
|
|
11
|
+
- **RxNorm** - Drug terminology (NLM)
|
|
12
|
+
- **LOINC** - Laboratory codes (Regenstrief Institute)
|
|
13
|
+
- **ICD-10** - Diagnosis and procedure codes (CMS)
|
|
14
|
+
- **CVX/MVX** - Vaccine codes (CDC)
|
|
15
|
+
- **FDA Drug Labels** - Prescribing information
|
|
16
|
+
|
|
17
|
+
When you ask Claude about medications, providers, or clinical codes, it can look up accurate, current information instead of relying on training data.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
1. **Claude Desktop** - Download from [claude.ai/download](https://claude.ai/download)
|
|
22
|
+
2. **Node.js 18+** - Download from [nodejs.org](https://nodejs.org)
|
|
23
|
+
3. **FHIRfly API Key** - Get one at [fhirfly.io](https://fhirfly.io) (free tier available)
|
|
24
|
+
|
|
25
|
+
## Quick Setup
|
|
26
|
+
|
|
27
|
+
### Step 1: Get a FHIRfly API Key
|
|
28
|
+
|
|
29
|
+
1. Go to [fhirfly.io](https://fhirfly.io) and sign up
|
|
30
|
+
2. Navigate to **Dashboard > Credentials**
|
|
31
|
+
3. Click **Create Credential** and select **MCP (Claude Desktop)**
|
|
32
|
+
4. Copy your API key (starts with `ffly_`)
|
|
33
|
+
|
|
34
|
+
### Step 2: Configure Claude Desktop
|
|
35
|
+
|
|
36
|
+
Find your Claude Desktop config file:
|
|
37
|
+
|
|
38
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
39
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
40
|
+
|
|
41
|
+
Add the FHIRfly server configuration:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"fhirfly": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "@fhirfly-io/mcp-server"],
|
|
49
|
+
"env": {
|
|
50
|
+
"FHIRFLY_API_KEY": "ffly_live_your_key_here"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Replace `ffly_live_your_key_here` with your actual API key.
|
|
58
|
+
|
|
59
|
+
### Step 3: Restart Claude Desktop
|
|
60
|
+
|
|
61
|
+
Completely quit Claude Desktop and reopen it. The FHIRfly tools should now be available.
|
|
62
|
+
|
|
63
|
+
## Verify It Works
|
|
64
|
+
|
|
65
|
+
Try asking Claude:
|
|
66
|
+
|
|
67
|
+
- "What is NDC 0069-0151-01?"
|
|
68
|
+
- "Look up NPI 1234567893"
|
|
69
|
+
- "Search for COVID vaccines in the CVX database"
|
|
70
|
+
- "What are the drug interactions for Lipitor?"
|
|
71
|
+
|
|
72
|
+
Claude should use the FHIRfly tools to look up real data.
|
|
73
|
+
|
|
74
|
+
## Available Tools
|
|
75
|
+
|
|
76
|
+
| Tool | Description |
|
|
77
|
+
|------|-------------|
|
|
78
|
+
| `ndc_get` | Look up drug by NDC code |
|
|
79
|
+
| `ndc_search` | Search drugs by name, ingredient, etc. |
|
|
80
|
+
| `npi_get` | Look up provider by NPI number |
|
|
81
|
+
| `npi_search` | Search providers by name, specialty, location |
|
|
82
|
+
| `rxnorm_get` | Look up drug by RxCUI |
|
|
83
|
+
| `rxnorm_search` | Search drug terminology |
|
|
84
|
+
| `loinc_get` | Look up lab test by LOINC code |
|
|
85
|
+
| `loinc_search` | Search lab codes |
|
|
86
|
+
| `icd10_get` | Look up diagnosis/procedure code |
|
|
87
|
+
| `icd10_search` | Search ICD-10 codes |
|
|
88
|
+
| `cvx_get` | Look up vaccine by CVX code |
|
|
89
|
+
| `cvx_search` | Search vaccine codes |
|
|
90
|
+
| `mvx_get` | Look up vaccine manufacturer |
|
|
91
|
+
| `mvx_search` | Search manufacturers |
|
|
92
|
+
| `fda_label_lookup` | Look up FDA drug label |
|
|
93
|
+
| `fda_label_search` | Search drug labels |
|
|
94
|
+
| `fda_label_safety` | Get safety info (boxed warnings, contraindications) |
|
|
95
|
+
| `fda_label_interactions` | Get drug interaction info |
|
|
96
|
+
| `fda_label_dosing` | Get dosing information |
|
|
97
|
+
| `fda_label_sections` | Get specific label sections |
|
|
98
|
+
|
|
99
|
+
## Configuration Options
|
|
100
|
+
|
|
101
|
+
| Environment Variable | Description | Default |
|
|
102
|
+
|---------------------|-------------|---------|
|
|
103
|
+
| `FHIRFLY_API_KEY` | Your FHIRfly API key (required) | - |
|
|
104
|
+
| `FHIRFLY_API_URL` | API base URL | `https://api.fhirfly.io` |
|
|
105
|
+
| `FHIRFLY_DEBUG` | Enable debug logging (`1` or `true`) | `false` |
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
### "FHIRFLY_API_KEY environment variable is required"
|
|
110
|
+
|
|
111
|
+
Your API key isn't configured. Make sure:
|
|
112
|
+
1. You have a valid API key from [fhirfly.io](https://fhirfly.io)
|
|
113
|
+
2. It's set in the `env` section of your Claude Desktop config
|
|
114
|
+
3. The key starts with `ffly_`
|
|
115
|
+
|
|
116
|
+
### "Invalid API key format"
|
|
117
|
+
|
|
118
|
+
FHIRfly API keys start with `ffly_`. Check that you copied the full key.
|
|
119
|
+
|
|
120
|
+
### "Authentication failed"
|
|
121
|
+
|
|
122
|
+
Your API key may be invalid or expired. Generate a new one at [fhirfly.io/dashboard/credentials](https://fhirfly.io/dashboard/credentials).
|
|
123
|
+
|
|
124
|
+
### Claude doesn't show FHIRfly tools
|
|
125
|
+
|
|
126
|
+
1. Make sure you completely quit and restarted Claude Desktop
|
|
127
|
+
2. Check your config file syntax (must be valid JSON)
|
|
128
|
+
3. Enable debug mode to see what's happening:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"mcpServers": {
|
|
133
|
+
"fhirfly": {
|
|
134
|
+
"command": "npx",
|
|
135
|
+
"args": ["-y", "@fhirfly-io/mcp-server"],
|
|
136
|
+
"env": {
|
|
137
|
+
"FHIRFLY_API_KEY": "your_key",
|
|
138
|
+
"FHIRFLY_DEBUG": "1"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### "Rate limit exceeded"
|
|
146
|
+
|
|
147
|
+
You've hit your plan's rate limit. Wait a moment and try again, or upgrade your plan at [fhirfly.io](https://fhirfly.io).
|
|
148
|
+
|
|
149
|
+
## How It Works
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
153
|
+
│ Claude Desktop │────▶│ This Package │────▶│ FHIRfly API │
|
|
154
|
+
│ │ │ (runs locally) │ │ (cloud) │
|
|
155
|
+
│ "What is │◀────│ │◀────│ │
|
|
156
|
+
│ NDC 123..." │ │ Translates MCP │ │ Returns drug │
|
|
157
|
+
│ │ │ ↔ HTTPS │ │ data │
|
|
158
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This package runs on your computer as a bridge between Claude Desktop and the FHIRfly API. It:
|
|
162
|
+
|
|
163
|
+
1. Receives requests from Claude Desktop via stdin
|
|
164
|
+
2. Translates them to HTTPS requests to FHIRfly
|
|
165
|
+
3. Returns the results to Claude via stdout
|
|
166
|
+
|
|
167
|
+
Your API key is sent to FHIRfly over HTTPS. No healthcare data is stored locally.
|
|
168
|
+
|
|
169
|
+
## Links
|
|
170
|
+
|
|
171
|
+
- [FHIRfly Documentation](https://fhirfly.io/docs)
|
|
172
|
+
- [MCP Setup Guide](https://fhirfly.io/docs/mcp/claude-desktop)
|
|
173
|
+
- [Get an API Key](https://fhirfly.io)
|
|
174
|
+
- [Report Issues](https://github.com/FHIRfly-io/fhirfly-mcp-server/issues)
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT - see [LICENSE](./LICENSE)
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
|
|
4
|
+
var StdioTransport = class {
|
|
5
|
+
handler = null;
|
|
6
|
+
debug;
|
|
7
|
+
pendingRequests = 0;
|
|
8
|
+
stdinClosed = false;
|
|
9
|
+
constructor(debug = false) {
|
|
10
|
+
this.debug = debug;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Set the handler for incoming messages
|
|
14
|
+
*/
|
|
15
|
+
setHandler(handler) {
|
|
16
|
+
this.handler = handler;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Start listening for messages on stdin
|
|
20
|
+
*/
|
|
21
|
+
start() {
|
|
22
|
+
const rl = createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
terminal: false
|
|
26
|
+
});
|
|
27
|
+
let buffer = "";
|
|
28
|
+
rl.on("line", async (line) => {
|
|
29
|
+
buffer += line;
|
|
30
|
+
try {
|
|
31
|
+
const request = JSON.parse(buffer);
|
|
32
|
+
buffer = "";
|
|
33
|
+
if (this.debug) {
|
|
34
|
+
console.error("[MCP DEBUG] Received:", JSON.stringify(request, null, 2));
|
|
35
|
+
}
|
|
36
|
+
if (this.handler) {
|
|
37
|
+
this.pendingRequests++;
|
|
38
|
+
try {
|
|
39
|
+
const response = await this.handler(request);
|
|
40
|
+
this.send(response);
|
|
41
|
+
} finally {
|
|
42
|
+
this.pendingRequests--;
|
|
43
|
+
this.maybeExit();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
if (line.trim().endsWith("}")) {
|
|
48
|
+
this.sendError(null, -32700, "Parse error: Invalid JSON");
|
|
49
|
+
buffer = "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
rl.on("close", () => {
|
|
54
|
+
this.stdinClosed = true;
|
|
55
|
+
if (this.debug) {
|
|
56
|
+
console.error("[MCP DEBUG] stdin closed, waiting for pending requests...");
|
|
57
|
+
}
|
|
58
|
+
this.maybeExit();
|
|
59
|
+
});
|
|
60
|
+
process.stdin.on("error", (err) => {
|
|
61
|
+
if (this.debug) {
|
|
62
|
+
console.error("[MCP DEBUG] stdin error:", err.message);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Exit if stdin is closed and no pending requests
|
|
68
|
+
*/
|
|
69
|
+
maybeExit() {
|
|
70
|
+
if (this.stdinClosed && this.pendingRequests === 0) {
|
|
71
|
+
if (this.debug) {
|
|
72
|
+
console.error("[MCP DEBUG] All requests complete, exiting");
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Send a JSON-RPC response to stdout
|
|
79
|
+
*/
|
|
80
|
+
send(response) {
|
|
81
|
+
const message = JSON.stringify(response);
|
|
82
|
+
if (this.debug) {
|
|
83
|
+
console.error("[MCP DEBUG] Sending:", JSON.stringify(response, null, 2));
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write(message + "\n");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Send an error response
|
|
89
|
+
*/
|
|
90
|
+
sendError(id, code, message, data) {
|
|
91
|
+
this.send({
|
|
92
|
+
jsonrpc: "2.0",
|
|
93
|
+
id,
|
|
94
|
+
error: {
|
|
95
|
+
code,
|
|
96
|
+
message,
|
|
97
|
+
data
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/client.ts
|
|
104
|
+
var FhirflyClient = class {
|
|
105
|
+
config;
|
|
106
|
+
constructor(config) {
|
|
107
|
+
this.config = config;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Send a JSON-RPC request to the FHIRfly MCP endpoint
|
|
111
|
+
*/
|
|
112
|
+
async request(method, params) {
|
|
113
|
+
const requestBody = {
|
|
114
|
+
jsonrpc: "2.0",
|
|
115
|
+
id: Date.now(),
|
|
116
|
+
method,
|
|
117
|
+
params
|
|
118
|
+
};
|
|
119
|
+
if (this.config.debug) {
|
|
120
|
+
console.error(`[MCP DEBUG] API Request: ${method}`, params);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch(`${this.config.apiUrl}/mcp`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"x-api-key": this.config.apiKey,
|
|
128
|
+
"User-Agent": "@fhirfly-io/mcp-server"
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(requestBody)
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
if (response.status === 401) {
|
|
134
|
+
return {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id: requestBody.id,
|
|
137
|
+
error: {
|
|
138
|
+
code: -32001,
|
|
139
|
+
message: "Authentication failed. Check your FHIRFLY_API_KEY."
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (response.status === 429) {
|
|
144
|
+
return {
|
|
145
|
+
jsonrpc: "2.0",
|
|
146
|
+
id: requestBody.id,
|
|
147
|
+
error: {
|
|
148
|
+
code: -32002,
|
|
149
|
+
message: "Rate limit exceeded. Please slow down requests."
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
jsonrpc: "2.0",
|
|
155
|
+
id: requestBody.id,
|
|
156
|
+
error: {
|
|
157
|
+
code: -32e3,
|
|
158
|
+
message: `API error: ${response.status} ${response.statusText}`
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const result = await response.json();
|
|
163
|
+
if (this.config.debug) {
|
|
164
|
+
console.error("[MCP DEBUG] API Response:", JSON.stringify(result, null, 2));
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
169
|
+
if (this.config.debug) {
|
|
170
|
+
console.error("[MCP DEBUG] API Error:", message);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
jsonrpc: "2.0",
|
|
174
|
+
id: requestBody.id,
|
|
175
|
+
error: {
|
|
176
|
+
code: -32003,
|
|
177
|
+
message: `Network error: ${message}`
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get list of available tools from FHIRfly
|
|
184
|
+
*/
|
|
185
|
+
async listTools() {
|
|
186
|
+
const response = await this.request("tools/list", {});
|
|
187
|
+
if (response.error) {
|
|
188
|
+
throw new Error(response.error.message);
|
|
189
|
+
}
|
|
190
|
+
return response.result;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Call a tool on the FHIRfly API
|
|
194
|
+
*/
|
|
195
|
+
async callTool(name, args) {
|
|
196
|
+
const response = await this.request("tools/call", {
|
|
197
|
+
name,
|
|
198
|
+
arguments: args
|
|
199
|
+
});
|
|
200
|
+
if (response.error) {
|
|
201
|
+
return {
|
|
202
|
+
content: [
|
|
203
|
+
{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: JSON.stringify({
|
|
206
|
+
error: response.error.message,
|
|
207
|
+
code: response.error.code
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
isError: true
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return response.result;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/server.ts
|
|
219
|
+
var MCP_ERRORS = {
|
|
220
|
+
PARSE_ERROR: -32700,
|
|
221
|
+
INVALID_REQUEST: -32600,
|
|
222
|
+
METHOD_NOT_FOUND: -32601,
|
|
223
|
+
INVALID_PARAMS: -32602,
|
|
224
|
+
INTERNAL_ERROR: -32603
|
|
225
|
+
};
|
|
226
|
+
var SERVER_INFO = {
|
|
227
|
+
name: "fhirfly-mcp-server",
|
|
228
|
+
version: "0.1.0"
|
|
229
|
+
};
|
|
230
|
+
var SERVER_CAPABILITIES = {
|
|
231
|
+
tools: {}
|
|
232
|
+
};
|
|
233
|
+
var McpServer = class {
|
|
234
|
+
transport;
|
|
235
|
+
client;
|
|
236
|
+
config;
|
|
237
|
+
initialized = false;
|
|
238
|
+
constructor(config) {
|
|
239
|
+
this.config = config;
|
|
240
|
+
this.transport = new StdioTransport(config.debug);
|
|
241
|
+
this.client = new FhirflyClient(config);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Start the MCP server
|
|
245
|
+
*/
|
|
246
|
+
start() {
|
|
247
|
+
if (this.config.debug) {
|
|
248
|
+
console.error("[MCP DEBUG] Starting FHIRfly MCP Server");
|
|
249
|
+
console.error("[MCP DEBUG] API URL:", this.config.apiUrl);
|
|
250
|
+
console.error("[MCP DEBUG] API Key:", this.config.apiKey.slice(0, 10) + "...");
|
|
251
|
+
}
|
|
252
|
+
this.transport.setHandler(this.handleMessage.bind(this));
|
|
253
|
+
this.transport.start();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Handle incoming JSON-RPC messages
|
|
257
|
+
*/
|
|
258
|
+
async handleMessage(request) {
|
|
259
|
+
try {
|
|
260
|
+
if (request.jsonrpc !== "2.0" || !request.method) {
|
|
261
|
+
return {
|
|
262
|
+
jsonrpc: "2.0",
|
|
263
|
+
id: request.id ?? null,
|
|
264
|
+
error: {
|
|
265
|
+
code: MCP_ERRORS.INVALID_REQUEST,
|
|
266
|
+
message: "Invalid JSON-RPC request"
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
switch (request.method) {
|
|
271
|
+
case "initialize":
|
|
272
|
+
return this.handleInitialize(request);
|
|
273
|
+
case "initialized":
|
|
274
|
+
this.initialized = true;
|
|
275
|
+
return {
|
|
276
|
+
jsonrpc: "2.0",
|
|
277
|
+
id: request.id,
|
|
278
|
+
result: {}
|
|
279
|
+
};
|
|
280
|
+
case "tools/list":
|
|
281
|
+
return this.handleToolsList(request);
|
|
282
|
+
case "tools/call":
|
|
283
|
+
return this.handleToolsCall(request);
|
|
284
|
+
case "ping":
|
|
285
|
+
return {
|
|
286
|
+
jsonrpc: "2.0",
|
|
287
|
+
id: request.id,
|
|
288
|
+
result: {}
|
|
289
|
+
};
|
|
290
|
+
default:
|
|
291
|
+
return {
|
|
292
|
+
jsonrpc: "2.0",
|
|
293
|
+
id: request.id,
|
|
294
|
+
error: {
|
|
295
|
+
code: MCP_ERRORS.METHOD_NOT_FOUND,
|
|
296
|
+
message: `Unknown method: ${request.method}`
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
if (this.config.debug) {
|
|
303
|
+
console.error("[MCP DEBUG] Handler error:", message);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
jsonrpc: "2.0",
|
|
307
|
+
id: request.id,
|
|
308
|
+
error: {
|
|
309
|
+
code: MCP_ERRORS.INTERNAL_ERROR,
|
|
310
|
+
message: `Internal error: ${message}`
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Handle initialize request
|
|
317
|
+
*/
|
|
318
|
+
async handleInitialize(request) {
|
|
319
|
+
if (this.config.debug) {
|
|
320
|
+
console.error("[MCP DEBUG] Initialize request from client");
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
jsonrpc: "2.0",
|
|
324
|
+
id: request.id,
|
|
325
|
+
result: {
|
|
326
|
+
protocolVersion: "2024-11-05",
|
|
327
|
+
serverInfo: SERVER_INFO,
|
|
328
|
+
capabilities: SERVER_CAPABILITIES
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Handle tools/list request
|
|
334
|
+
*/
|
|
335
|
+
async handleToolsList(request) {
|
|
336
|
+
try {
|
|
337
|
+
const result = await this.client.listTools();
|
|
338
|
+
return {
|
|
339
|
+
jsonrpc: "2.0",
|
|
340
|
+
id: request.id,
|
|
341
|
+
result
|
|
342
|
+
};
|
|
343
|
+
} catch (error) {
|
|
344
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
345
|
+
return {
|
|
346
|
+
jsonrpc: "2.0",
|
|
347
|
+
id: request.id,
|
|
348
|
+
error: {
|
|
349
|
+
code: MCP_ERRORS.INTERNAL_ERROR,
|
|
350
|
+
message: `Failed to list tools: ${message}`
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Handle tools/call request
|
|
357
|
+
*/
|
|
358
|
+
async handleToolsCall(request) {
|
|
359
|
+
const params = request.params;
|
|
360
|
+
if (!params?.name) {
|
|
361
|
+
return {
|
|
362
|
+
jsonrpc: "2.0",
|
|
363
|
+
id: request.id,
|
|
364
|
+
error: {
|
|
365
|
+
code: MCP_ERRORS.INVALID_PARAMS,
|
|
366
|
+
message: "Missing tool name in params"
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const result = await this.client.callTool(params.name, params.arguments ?? {});
|
|
372
|
+
return {
|
|
373
|
+
jsonrpc: "2.0",
|
|
374
|
+
id: request.id,
|
|
375
|
+
result
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
379
|
+
return {
|
|
380
|
+
jsonrpc: "2.0",
|
|
381
|
+
id: request.id,
|
|
382
|
+
error: {
|
|
383
|
+
code: MCP_ERRORS.INTERNAL_ERROR,
|
|
384
|
+
message: `Tool call failed: ${message}`
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// src/cli.ts
|
|
392
|
+
var API_KEY = process.env.FHIRFLY_API_KEY;
|
|
393
|
+
var API_URL = process.env.FHIRFLY_API_URL || "https://api.fhirfly.io";
|
|
394
|
+
var DEBUG = process.env.FHIRFLY_DEBUG === "1" || process.env.FHIRFLY_DEBUG === "true";
|
|
395
|
+
if (!API_KEY) {
|
|
396
|
+
console.error("Error: FHIRFLY_API_KEY environment variable is required");
|
|
397
|
+
console.error("");
|
|
398
|
+
console.error("To get an API key:");
|
|
399
|
+
console.error(" 1. Sign up at https://fhirfly.io");
|
|
400
|
+
console.error(" 2. Go to Dashboard > Credentials");
|
|
401
|
+
console.error(" 3. Create an MCP credential");
|
|
402
|
+
console.error("");
|
|
403
|
+
console.error("Then set the environment variable:");
|
|
404
|
+
console.error(' export FHIRFLY_API_KEY="your_api_key_here"');
|
|
405
|
+
console.error("");
|
|
406
|
+
console.error("Or configure in Claude Desktop's claude_desktop_config.json");
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
if (!API_KEY.startsWith("ffly_")) {
|
|
410
|
+
console.error("Error: Invalid API key format");
|
|
411
|
+
console.error("FHIRfly API keys start with 'ffly_'");
|
|
412
|
+
console.error("");
|
|
413
|
+
console.error("Please check your FHIRFLY_API_KEY environment variable.");
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
if (DEBUG) {
|
|
417
|
+
console.error("=".repeat(50));
|
|
418
|
+
console.error("FHIRfly MCP Server - Debug Mode");
|
|
419
|
+
console.error("=".repeat(50));
|
|
420
|
+
console.error("API URL:", API_URL);
|
|
421
|
+
console.error("API Key:", API_KEY.slice(0, 15) + "...");
|
|
422
|
+
console.error("=".repeat(50));
|
|
423
|
+
}
|
|
424
|
+
var server = new McpServer({
|
|
425
|
+
apiKey: API_KEY,
|
|
426
|
+
apiUrl: API_URL,
|
|
427
|
+
debug: DEBUG
|
|
428
|
+
});
|
|
429
|
+
server.start();
|
|
430
|
+
//# sourceMappingURL=cli.js.map
|
|
431
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transport.ts","../src/client.ts","../src/server.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * MCP Stdio Transport\n * Handles JSON-RPC communication over stdin/stdout\n */\n\nimport { createInterface } from \"readline\";\nimport type { JsonRpcRequest, JsonRpcResponse } from \"./types.js\";\n\nexport type MessageHandler = (request: JsonRpcRequest) => Promise<JsonRpcResponse>;\n\nexport class StdioTransport {\n private handler: MessageHandler | null = null;\n private debug: boolean;\n private pendingRequests: number = 0;\n private stdinClosed: boolean = false;\n\n constructor(debug: boolean = false) {\n this.debug = debug;\n }\n\n /**\n * Set the handler for incoming messages\n */\n setHandler(handler: MessageHandler): void {\n this.handler = handler;\n }\n\n /**\n * Start listening for messages on stdin\n */\n start(): void {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: false,\n });\n\n // Buffer for accumulating partial messages\n let buffer = \"\";\n\n rl.on(\"line\", async (line) => {\n buffer += line;\n\n // Try to parse as JSON\n try {\n const request = JSON.parse(buffer) as JsonRpcRequest;\n buffer = \"\"; // Clear buffer on successful parse\n\n if (this.debug) {\n console.error(\"[MCP DEBUG] Received:\", JSON.stringify(request, null, 2));\n }\n\n if (this.handler) {\n this.pendingRequests++;\n try {\n const response = await this.handler(request);\n this.send(response);\n } finally {\n this.pendingRequests--;\n this.maybeExit();\n }\n }\n } catch {\n // If parse fails, might be partial message - keep buffering\n // Unless it looks like a complete but invalid message\n if (line.trim().endsWith(\"}\")) {\n this.sendError(null, -32700, \"Parse error: Invalid JSON\");\n buffer = \"\";\n }\n }\n });\n\n rl.on(\"close\", () => {\n this.stdinClosed = true;\n if (this.debug) {\n console.error(\"[MCP DEBUG] stdin closed, waiting for pending requests...\");\n }\n this.maybeExit();\n });\n\n // Handle errors gracefully\n process.stdin.on(\"error\", (err) => {\n if (this.debug) {\n console.error(\"[MCP DEBUG] stdin error:\", err.message);\n }\n });\n }\n\n /**\n * Exit if stdin is closed and no pending requests\n */\n private maybeExit(): void {\n if (this.stdinClosed && this.pendingRequests === 0) {\n if (this.debug) {\n console.error(\"[MCP DEBUG] All requests complete, exiting\");\n }\n process.exit(0);\n }\n }\n\n /**\n * Send a JSON-RPC response to stdout\n */\n send(response: JsonRpcResponse): void {\n const message = JSON.stringify(response);\n\n if (this.debug) {\n console.error(\"[MCP DEBUG] Sending:\", JSON.stringify(response, null, 2));\n }\n\n // Write to stdout with newline\n process.stdout.write(message + \"\\n\");\n }\n\n /**\n * Send an error response\n */\n sendError(id: string | number | null, code: number, message: string, data?: unknown): void {\n this.send({\n jsonrpc: \"2.0\",\n id,\n error: {\n code,\n message,\n data,\n },\n });\n }\n}\n","/**\n * FHIRfly API Client\n * Makes HTTP requests to the FHIRfly MCP endpoint\n */\n\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpServerConfig,\n McpToolsListResult,\n McpToolCallResult,\n} from \"./types.js\";\n\nexport class FhirflyClient {\n private config: McpServerConfig;\n\n constructor(config: McpServerConfig) {\n this.config = config;\n }\n\n /**\n * Send a JSON-RPC request to the FHIRfly MCP endpoint\n */\n async request(method: string, params?: Record<string, unknown>): Promise<JsonRpcResponse> {\n const requestBody: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n id: Date.now(),\n method,\n params,\n };\n\n if (this.config.debug) {\n console.error(`[MCP DEBUG] API Request: ${method}`, params);\n }\n\n try {\n const response = await fetch(`${this.config.apiUrl}/mcp`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.config.apiKey,\n \"User-Agent\": \"@fhirfly-io/mcp-server\",\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n // Handle HTTP errors\n if (response.status === 401) {\n return {\n jsonrpc: \"2.0\",\n id: requestBody.id,\n error: {\n code: -32001,\n message: \"Authentication failed. Check your FHIRFLY_API_KEY.\",\n },\n };\n }\n\n if (response.status === 429) {\n return {\n jsonrpc: \"2.0\",\n id: requestBody.id,\n error: {\n code: -32002,\n message: \"Rate limit exceeded. Please slow down requests.\",\n },\n };\n }\n\n return {\n jsonrpc: \"2.0\",\n id: requestBody.id,\n error: {\n code: -32000,\n message: `API error: ${response.status} ${response.statusText}`,\n },\n };\n }\n\n const result = (await response.json()) as JsonRpcResponse;\n\n if (this.config.debug) {\n console.error(\"[MCP DEBUG] API Response:\", JSON.stringify(result, null, 2));\n }\n\n return result;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (this.config.debug) {\n console.error(\"[MCP DEBUG] API Error:\", message);\n }\n\n return {\n jsonrpc: \"2.0\",\n id: requestBody.id,\n error: {\n code: -32003,\n message: `Network error: ${message}`,\n },\n };\n }\n }\n\n /**\n * Get list of available tools from FHIRfly\n */\n async listTools(): Promise<McpToolsListResult> {\n const response = await this.request(\"tools/list\", {});\n\n if (response.error) {\n throw new Error(response.error.message);\n }\n\n return response.result as McpToolsListResult;\n }\n\n /**\n * Call a tool on the FHIRfly API\n */\n async callTool(name: string, args: Record<string, unknown>): Promise<McpToolCallResult> {\n const response = await this.request(\"tools/call\", {\n name,\n arguments: args,\n });\n\n if (response.error) {\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({\n error: response.error.message,\n code: response.error.code,\n }),\n },\n ],\n isError: true,\n };\n }\n\n return response.result as McpToolCallResult;\n }\n}\n","/**\n * FHIRfly MCP Server\n * Main server class that handles MCP protocol messages\n */\n\nimport { StdioTransport } from \"./transport.js\";\nimport { FhirflyClient } from \"./client.js\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpServerConfig,\n McpToolCallParams,\n} from \"./types.js\";\n\nconst MCP_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n} as const;\n\n// Server capabilities\nconst SERVER_INFO = {\n name: \"fhirfly-mcp-server\",\n version: \"0.1.0\",\n};\n\nconst SERVER_CAPABILITIES = {\n tools: {},\n};\n\nexport class McpServer {\n private transport: StdioTransport;\n private client: FhirflyClient;\n private config: McpServerConfig;\n public initialized: boolean = false;\n\n constructor(config: McpServerConfig) {\n this.config = config;\n this.transport = new StdioTransport(config.debug);\n this.client = new FhirflyClient(config);\n }\n\n /**\n * Start the MCP server\n */\n start(): void {\n if (this.config.debug) {\n console.error(\"[MCP DEBUG] Starting FHIRfly MCP Server\");\n console.error(\"[MCP DEBUG] API URL:\", this.config.apiUrl);\n console.error(\"[MCP DEBUG] API Key:\", this.config.apiKey.slice(0, 10) + \"...\");\n }\n\n this.transport.setHandler(this.handleMessage.bind(this));\n this.transport.start();\n }\n\n /**\n * Handle incoming JSON-RPC messages\n */\n private async handleMessage(request: JsonRpcRequest): Promise<JsonRpcResponse> {\n try {\n // Validate JSON-RPC request structure\n if (request.jsonrpc !== \"2.0\" || !request.method) {\n return {\n jsonrpc: \"2.0\",\n id: request.id ?? null,\n error: {\n code: MCP_ERRORS.INVALID_REQUEST,\n message: \"Invalid JSON-RPC request\",\n },\n };\n }\n\n // Route to appropriate handler\n switch (request.method) {\n case \"initialize\":\n return this.handleInitialize(request);\n\n case \"initialized\":\n // Client notification that initialization is complete\n this.initialized = true;\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n result: {},\n };\n\n case \"tools/list\":\n return this.handleToolsList(request);\n\n case \"tools/call\":\n return this.handleToolsCall(request);\n\n case \"ping\":\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n result: {},\n };\n\n default:\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: MCP_ERRORS.METHOD_NOT_FOUND,\n message: `Unknown method: ${request.method}`,\n },\n };\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (this.config.debug) {\n console.error(\"[MCP DEBUG] Handler error:\", message);\n }\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: MCP_ERRORS.INTERNAL_ERROR,\n message: `Internal error: ${message}`,\n },\n };\n }\n }\n\n /**\n * Handle initialize request\n */\n private async handleInitialize(request: JsonRpcRequest): Promise<JsonRpcResponse> {\n if (this.config.debug) {\n console.error(\"[MCP DEBUG] Initialize request from client\");\n }\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n result: {\n protocolVersion: \"2024-11-05\",\n serverInfo: SERVER_INFO,\n capabilities: SERVER_CAPABILITIES,\n },\n };\n }\n\n /**\n * Handle tools/list request\n */\n private async handleToolsList(request: JsonRpcRequest): Promise<JsonRpcResponse> {\n try {\n const result = await this.client.listTools();\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n result,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: MCP_ERRORS.INTERNAL_ERROR,\n message: `Failed to list tools: ${message}`,\n },\n };\n }\n }\n\n /**\n * Handle tools/call request\n */\n private async handleToolsCall(request: JsonRpcRequest): Promise<JsonRpcResponse> {\n const params = request.params as McpToolCallParams | undefined;\n\n if (!params?.name) {\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: MCP_ERRORS.INVALID_PARAMS,\n message: \"Missing tool name in params\",\n },\n };\n }\n\n try {\n const result = await this.client.callTool(params.name, params.arguments ?? {});\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n result,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: MCP_ERRORS.INTERNAL_ERROR,\n message: `Tool call failed: ${message}`,\n },\n };\n }\n }\n}\n","/**\n * FHIRfly MCP Server CLI\n * Entry point for running as an MCP server\n */\n\nimport { McpServer } from \"./server.js\";\n\n// Configuration from environment\nconst API_KEY = process.env.FHIRFLY_API_KEY;\nconst API_URL = process.env.FHIRFLY_API_URL || \"https://api.fhirfly.io\";\nconst DEBUG = process.env.FHIRFLY_DEBUG === \"1\" || process.env.FHIRFLY_DEBUG === \"true\";\n\n// Validate required configuration\nif (!API_KEY) {\n console.error(\"Error: FHIRFLY_API_KEY environment variable is required\");\n console.error(\"\");\n console.error(\"To get an API key:\");\n console.error(\" 1. Sign up at https://fhirfly.io\");\n console.error(\" 2. Go to Dashboard > Credentials\");\n console.error(\" 3. Create an MCP credential\");\n console.error(\"\");\n console.error(\"Then set the environment variable:\");\n console.error(' export FHIRFLY_API_KEY=\"your_api_key_here\"');\n console.error(\"\");\n console.error(\"Or configure in Claude Desktop's claude_desktop_config.json\");\n process.exit(1);\n}\n\n// Validate API key format\nif (!API_KEY.startsWith(\"ffly_\")) {\n console.error(\"Error: Invalid API key format\");\n console.error(\"FHIRfly API keys start with 'ffly_'\");\n console.error(\"\");\n console.error(\"Please check your FHIRFLY_API_KEY environment variable.\");\n process.exit(1);\n}\n\nif (DEBUG) {\n console.error(\"=\".repeat(50));\n console.error(\"FHIRfly MCP Server - Debug Mode\");\n console.error(\"=\".repeat(50));\n console.error(\"API URL:\", API_URL);\n console.error(\"API Key:\", API_KEY.slice(0, 15) + \"...\");\n console.error(\"=\".repeat(50));\n}\n\n// Create and start server\nconst server = new McpServer({\n apiKey: API_KEY,\n apiUrl: API_URL,\n debug: DEBUG,\n});\n\nserver.start();\n"]}
|