@iflow-mcp/kamranbiglari-mcp-server-chart 1.0.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 +264 -0
- package/biome.json +49 -0
- package/dist/charts/bar.js +43 -0
- package/dist/charts/bubble.js +71 -0
- package/dist/charts/doughnut.js +104 -0
- package/dist/charts/gauge.js +84 -0
- package/dist/charts/index.js +15 -0
- package/dist/charts/line.js +74 -0
- package/dist/charts/ohlc.js +455 -0
- package/dist/charts/pie.js +50 -0
- package/dist/charts/polarArea.js +50 -0
- package/dist/charts/progressBar.js +50 -0
- package/dist/charts/radar.js +59 -0
- package/dist/charts/radialGauge.js +43 -0
- package/dist/charts/sankey.js +64 -0
- package/dist/charts/scatter.js +70 -0
- package/dist/charts/sparkline.js +43 -0
- package/dist/charts/violin.js +67 -0
- package/dist/index.js +236 -0
- package/dist/stdio-server.js +58 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/schema.js +9 -0
- package/image.png +0 -0
- package/language.json +1 -0
- package/package.json +1 -0
- package/package_name +1 -0
- package/push_info.json +5 -0
- package/screenshot/image.png +0 -0
- package/src/charts/bar.ts +48 -0
- package/src/charts/bubble.ts +78 -0
- package/src/charts/doughnut.ts +117 -0
- package/src/charts/gauge.ts +92 -0
- package/src/charts/index.ts +15 -0
- package/src/charts/line.ts +80 -0
- package/src/charts/ohlc.ts +474 -0
- package/src/charts/pie.ts +56 -0
- package/src/charts/polarArea.ts +56 -0
- package/src/charts/progressBar.ts +56 -0
- package/src/charts/radar.ts +65 -0
- package/src/charts/radialGauge.ts +49 -0
- package/src/charts/sankey.ts +71 -0
- package/src/charts/scatter.ts +77 -0
- package/src/charts/sparkline.ts +49 -0
- package/src/charts/violin.ts +73 -0
- package/src/index.ts +279 -0
- package/src/stdio-server.ts +77 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/schema.ts +10 -0
- package/tsconfig.json +1 -0
- package/worker-configuration.d.ts +5709 -0
- package/wrangler.jsonc +28 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the radar chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'radar',
|
|
7
|
+
// data: {
|
|
8
|
+
// labels: ['January', 'February', 'March', 'April', 'May'],
|
|
9
|
+
// datasets: [
|
|
10
|
+
// { label: 'Dogs', data: [50, 60, 70, 180, 190] },
|
|
11
|
+
// { label: 'Cats', data: [100, 200, 300, 400, 500] },
|
|
12
|
+
// ],
|
|
13
|
+
// },
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
const schema = {
|
|
17
|
+
type: z.literal("radar").default("radar"),
|
|
18
|
+
data: z.object({
|
|
19
|
+
labels: z.array(z.string()).describe("Labels for the radar chart axes"),
|
|
20
|
+
datasets: z.array(
|
|
21
|
+
z.object({
|
|
22
|
+
label: z.string().describe("Label for the dataset"),
|
|
23
|
+
data: z.array(z.number()).describe("Data points for each axis"),
|
|
24
|
+
backgroundColor: z.string().optional().describe("Fill color of the radar area"),
|
|
25
|
+
borderColor: z.string().optional().describe("Color of the radar border line"),
|
|
26
|
+
borderWidth: z.number().optional().describe("Width of the border line"),
|
|
27
|
+
pointBackgroundColor: z.string().optional().describe("Background color of the data points"),
|
|
28
|
+
pointBorderColor: z.string().optional().describe("Border color of the data points"),
|
|
29
|
+
pointRadius: z.number().optional().describe("Radius of the data points"),
|
|
30
|
+
fill: z.boolean().optional().describe("Whether to fill the area"),
|
|
31
|
+
})
|
|
32
|
+
),
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const tool = {
|
|
37
|
+
name: "radar",
|
|
38
|
+
description: `Generates a radar chart with the provided data.
|
|
39
|
+
example input:
|
|
40
|
+
\`\`\`json
|
|
41
|
+
{
|
|
42
|
+
"type": "radar",
|
|
43
|
+
"data": {
|
|
44
|
+
"labels": ["January", "February", "March", "April", "May"],
|
|
45
|
+
"datasets": [
|
|
46
|
+
{
|
|
47
|
+
"label": "Dogs",
|
|
48
|
+
"data": [50, 60, 70, 180, 190]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"label": "Cats",
|
|
52
|
+
"data": [100, 200, 300, 400, 500]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
\`\`\`
|
|
58
|
+
`,
|
|
59
|
+
inputSchema: zodToJsonSchema(schema),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const radar = {
|
|
63
|
+
schema,
|
|
64
|
+
tool,
|
|
65
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the radial gauge chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'radialGauge',
|
|
7
|
+
// data: { datasets: [{ data: [70], backgroundColor: 'green' }] },
|
|
8
|
+
// }
|
|
9
|
+
|
|
10
|
+
const schema = {
|
|
11
|
+
type: z.literal("radialGauge").default("radialGauge"),
|
|
12
|
+
data: z.object({
|
|
13
|
+
datasets: z.array(
|
|
14
|
+
z.object({
|
|
15
|
+
data: z.array(z.number()).describe("Single value array representing the gauge value"),
|
|
16
|
+
backgroundColor: z.string().optional().describe("Background color of the gauge"),
|
|
17
|
+
borderColor: z.string().optional().describe("Border color of the gauge"),
|
|
18
|
+
borderWidth: z.number().optional().describe("Width of the gauge border"),
|
|
19
|
+
label: z.string().optional().describe("Label for the dataset"),
|
|
20
|
+
})
|
|
21
|
+
),
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const tool = {
|
|
26
|
+
name: "radialGauge",
|
|
27
|
+
description: `Generates a radial gauge chart with the provided value.
|
|
28
|
+
example input:
|
|
29
|
+
\`\`\`json
|
|
30
|
+
{
|
|
31
|
+
"type": "radialGauge",
|
|
32
|
+
"data": {
|
|
33
|
+
"datasets": [
|
|
34
|
+
{
|
|
35
|
+
"data": [70],
|
|
36
|
+
"backgroundColor": "green"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
\`\`\`
|
|
42
|
+
`,
|
|
43
|
+
inputSchema: zodToJsonSchema(schema),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const radialGauge = {
|
|
47
|
+
schema,
|
|
48
|
+
tool,
|
|
49
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the sankey chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'sankey',
|
|
7
|
+
// data: {
|
|
8
|
+
// datasets: [
|
|
9
|
+
// {
|
|
10
|
+
// data: [
|
|
11
|
+
// { from: 'Step A', to: 'Step B', flow: 10 },
|
|
12
|
+
// { from: 'Step A', to: 'Step C', flow: 5 },
|
|
13
|
+
// { from: 'Step B', to: 'Step C', flow: 10 },
|
|
14
|
+
// { from: 'Step D', to: 'Step C', flow: 7 },
|
|
15
|
+
// ]
|
|
16
|
+
// },
|
|
17
|
+
// ],
|
|
18
|
+
// },
|
|
19
|
+
// }
|
|
20
|
+
|
|
21
|
+
const sankeyDataPointSchema = z.object({
|
|
22
|
+
from: z.string().describe("Source node"),
|
|
23
|
+
to: z.string().describe("Target node"),
|
|
24
|
+
flow: z.number().describe("Flow value between nodes"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const schema = {
|
|
28
|
+
type: z.literal("sankey").default("sankey"),
|
|
29
|
+
data: z.object({
|
|
30
|
+
datasets: z.array(
|
|
31
|
+
z.object({
|
|
32
|
+
data: z.array(sankeyDataPointSchema).describe("Array of flow connections between nodes"),
|
|
33
|
+
label: z.string().optional().describe("Label for the dataset"),
|
|
34
|
+
colorFrom: z.string().optional().describe("Color scheme for source nodes"),
|
|
35
|
+
colorTo: z.string().optional().describe("Color scheme for target nodes"),
|
|
36
|
+
colorMode: z.string().optional().describe("Color mode for the flows"),
|
|
37
|
+
borderWidth: z.number().optional().describe("Width of the flow borders"),
|
|
38
|
+
})
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const tool = {
|
|
44
|
+
name: "sankey",
|
|
45
|
+
description: `Generates a sankey diagram with the provided flow data.
|
|
46
|
+
example input:
|
|
47
|
+
\`\`\`json
|
|
48
|
+
{
|
|
49
|
+
"type": "sankey",
|
|
50
|
+
"data": {
|
|
51
|
+
"datasets": [
|
|
52
|
+
{
|
|
53
|
+
"data": [
|
|
54
|
+
{"from": "Step A", "to": "Step B", "flow": 10},
|
|
55
|
+
{"from": "Step A", "to": "Step C", "flow": 5},
|
|
56
|
+
{"from": "Step B", "to": "Step C", "flow": 10},
|
|
57
|
+
{"from": "Step D", "to": "Step C", "flow": 7}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
\`\`\`
|
|
64
|
+
`,
|
|
65
|
+
inputSchema: zodToJsonSchema(schema),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const sankey = {
|
|
69
|
+
schema,
|
|
70
|
+
tool,
|
|
71
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the scatter chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'scatter',
|
|
7
|
+
// data: {
|
|
8
|
+
// datasets: [
|
|
9
|
+
// {
|
|
10
|
+
// label: 'Data 1',
|
|
11
|
+
// data: [
|
|
12
|
+
// { x: 2, y: 4 },
|
|
13
|
+
// { x: 3, y: 3 },
|
|
14
|
+
// { x: -10, y: 0 },
|
|
15
|
+
// { x: 0, y: 10 },
|
|
16
|
+
// { x: 10, y: 5 },
|
|
17
|
+
// ],
|
|
18
|
+
// },
|
|
19
|
+
// ],
|
|
20
|
+
// },
|
|
21
|
+
// }
|
|
22
|
+
|
|
23
|
+
const scatterDataPointSchema = z.object({
|
|
24
|
+
x: z.number(),
|
|
25
|
+
y: z.number(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const schema = {
|
|
29
|
+
type: z.literal("scatter").default("scatter"),
|
|
30
|
+
data: z.object({
|
|
31
|
+
datasets: z.array(
|
|
32
|
+
z.object({
|
|
33
|
+
label: z.string().describe("Label for the dataset"),
|
|
34
|
+
data: z.array(scatterDataPointSchema).describe("Array of {x, y} coordinate points"),
|
|
35
|
+
backgroundColor: z.string().optional().describe("Background color of the points"),
|
|
36
|
+
borderColor: z.string().optional().describe("Border color of the points"),
|
|
37
|
+
borderWidth: z.number().optional().describe("Width of the point borders"),
|
|
38
|
+
pointRadius: z.number().optional().describe("Radius of the data points"),
|
|
39
|
+
pointBackgroundColor: z.string().optional().describe("Background color of the data points"),
|
|
40
|
+
pointBorderColor: z.string().optional().describe("Border color of the data points"),
|
|
41
|
+
showLine: z.boolean().optional().describe("Whether to show lines connecting the points"),
|
|
42
|
+
})
|
|
43
|
+
),
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const tool = {
|
|
48
|
+
name: "scatter",
|
|
49
|
+
description: `Generates a scatter chart with the provided data points.
|
|
50
|
+
example input:
|
|
51
|
+
\`\`\`json
|
|
52
|
+
{
|
|
53
|
+
"type": "scatter",
|
|
54
|
+
"data": {
|
|
55
|
+
"datasets": [
|
|
56
|
+
{
|
|
57
|
+
"label": "Data 1",
|
|
58
|
+
"data": [
|
|
59
|
+
{"x": 2, "y": 4},
|
|
60
|
+
{"x": 3, "y": 3},
|
|
61
|
+
{"x": -10, "y": 0},
|
|
62
|
+
{"x": 0, "y": 10},
|
|
63
|
+
{"x": 10, "y": 5}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
\`\`\`
|
|
70
|
+
`,
|
|
71
|
+
inputSchema: zodToJsonSchema(schema),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const scatter = {
|
|
75
|
+
schema,
|
|
76
|
+
tool,
|
|
77
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the sparkline chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'sparkline',
|
|
7
|
+
// data: { datasets: [{ data: [140, 60, 274, 370, 199] }] },
|
|
8
|
+
// }
|
|
9
|
+
|
|
10
|
+
const schema = {
|
|
11
|
+
type: z.literal("sparkline").default("sparkline"),
|
|
12
|
+
data: z.object({
|
|
13
|
+
datasets: z.array(
|
|
14
|
+
z.object({
|
|
15
|
+
data: z.array(z.number()).describe("Data values for the sparkline"),
|
|
16
|
+
backgroundColor: z.string().optional().describe("Background color of the sparkline"),
|
|
17
|
+
borderColor: z.string().optional().describe("Border color of the sparkline"),
|
|
18
|
+
borderWidth: z.number().optional().describe("Width of the sparkline border"),
|
|
19
|
+
fill: z.boolean().optional().describe("Whether to fill the area under the line"),
|
|
20
|
+
label: z.string().optional().describe("Label for the dataset"),
|
|
21
|
+
})
|
|
22
|
+
),
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const tool = {
|
|
27
|
+
name: "sparkline",
|
|
28
|
+
description: `Generates a sparkline chart with the provided data.
|
|
29
|
+
example input:
|
|
30
|
+
\`\`\`json
|
|
31
|
+
{
|
|
32
|
+
"type": "sparkline",
|
|
33
|
+
"data": {
|
|
34
|
+
"datasets": [
|
|
35
|
+
{
|
|
36
|
+
"data": [140, 60, 274, 370, 199]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
\`\`\`
|
|
42
|
+
`,
|
|
43
|
+
inputSchema: zodToJsonSchema(schema),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const sparkline = {
|
|
47
|
+
schema,
|
|
48
|
+
tool,
|
|
49
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodToJsonSchema } from "../utils/index.js";
|
|
3
|
+
|
|
4
|
+
// Define the schema for the violin chart
|
|
5
|
+
// {
|
|
6
|
+
// type: 'violin',
|
|
7
|
+
// data: {
|
|
8
|
+
// labels: [2012, 2013, 2014, 2015],
|
|
9
|
+
// datasets: [
|
|
10
|
+
// {
|
|
11
|
+
// label: 'Data',
|
|
12
|
+
// data: [
|
|
13
|
+
// [12, 6, 3, 4],
|
|
14
|
+
// [1, 8, 8, 15],
|
|
15
|
+
// [1, 1, 1, 2, 3, 5, 9, 8],
|
|
16
|
+
// [19, -3, 18, 8, 5, 9, 9],
|
|
17
|
+
// ],
|
|
18
|
+
// backgroundColor: 'rgba(56,123,45,0.2)',
|
|
19
|
+
// borderColor: 'rgba(56,123,45,1.9)',
|
|
20
|
+
// },
|
|
21
|
+
// ],
|
|
22
|
+
// },
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
const schema = {
|
|
26
|
+
type: z.literal("violin").default("violin"),
|
|
27
|
+
data: z.object({
|
|
28
|
+
labels: z.array(z.union([z.string(), z.number()])).describe("Labels for each violin"),
|
|
29
|
+
datasets: z.array(
|
|
30
|
+
z.object({
|
|
31
|
+
label: z.string().describe("Label for the dataset"),
|
|
32
|
+
data: z.array(z.array(z.number())).describe("Array of data arrays for each violin"),
|
|
33
|
+
backgroundColor: z.string().optional().describe("Background color of the violins"),
|
|
34
|
+
borderColor: z.string().optional().describe("Border color of the violins"),
|
|
35
|
+
borderWidth: z.number().optional().describe("Width of the violin borders"),
|
|
36
|
+
})
|
|
37
|
+
),
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const tool = {
|
|
42
|
+
name: "violin",
|
|
43
|
+
description: `Generates a violin chart with the provided distribution data.
|
|
44
|
+
example input:
|
|
45
|
+
\`\`\`json
|
|
46
|
+
{
|
|
47
|
+
"type": "violin",
|
|
48
|
+
"data": {
|
|
49
|
+
"labels": [2012, 2013, 2014, 2015],
|
|
50
|
+
"datasets": [
|
|
51
|
+
{
|
|
52
|
+
"label": "Data",
|
|
53
|
+
"data": [
|
|
54
|
+
[12, 6, 3, 4],
|
|
55
|
+
[1, 8, 8, 15],
|
|
56
|
+
[1, 1, 1, 2, 3, 5, 9, 8],
|
|
57
|
+
[19, -3, 18, 8, 5, 9, 9]
|
|
58
|
+
],
|
|
59
|
+
"backgroundColor": "rgba(56,123,45,0.2)",
|
|
60
|
+
"borderColor": "rgba(56,123,45,1.9)"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
\`\`\`
|
|
66
|
+
`,
|
|
67
|
+
inputSchema: zodToJsonSchema(schema),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const violin = {
|
|
71
|
+
schema,
|
|
72
|
+
tool,
|
|
73
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { McpAgent } from "agents/mcp";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import * as Charts from './charts';
|
|
5
|
+
|
|
6
|
+
// Define our MCP agent with tools
|
|
7
|
+
export class MyMCP extends McpAgent {
|
|
8
|
+
server = new McpServer({
|
|
9
|
+
name: "mcp-server-chart",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
description: "MCP Server for generating charts using QuickChart",
|
|
12
|
+
baseUrl: "https://chart.mcp.cloudcertainty.com",
|
|
13
|
+
author: "Kamran Biglari",
|
|
14
|
+
authorUrl: "https://github.com/KamranBiglari"
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
|
|
19
|
+
// Register all chart tools
|
|
20
|
+
for (const chart of Object.values(Charts)) {
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
this.server.tool(chart.tool.name, chart.tool.description, chart.schema, async (input) => {
|
|
23
|
+
|
|
24
|
+
console.log(`Received input for ${chart.tool.name}:`, input);
|
|
25
|
+
return fetch(`http://quickchart.io/chart?v=3&c=${encodeURIComponent(JSON.stringify(input))}`)
|
|
26
|
+
.then(response => response.arrayBuffer())
|
|
27
|
+
.then(data => {
|
|
28
|
+
const base64Image = Buffer.from(data).toString('base64');
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "image",
|
|
33
|
+
data: base64Image,
|
|
34
|
+
mimeType: "image/png",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
})
|
|
39
|
+
.catch(error => {
|
|
40
|
+
console.error(`Error generating ${chart.tool.name} chart:`, error);
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `Error generating ${chart.tool.name} chart: ${error.message}`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Helper function to get the base URL from the request
|
|
58
|
+
function getBaseUrl(request: Request): string {
|
|
59
|
+
const url = new URL(request.url);
|
|
60
|
+
return `${url.protocol}//${url.host}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// OAuth Authorization Server Discovery Response
|
|
64
|
+
function createOAuthAuthorizationServerResponse(baseUrl: string) {
|
|
65
|
+
return {
|
|
66
|
+
"issuer": baseUrl,
|
|
67
|
+
"authorization_endpoint": `${baseUrl}/oauth/authorize`,
|
|
68
|
+
"token_endpoint": `${baseUrl}/oauth/token`,
|
|
69
|
+
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
|
|
70
|
+
"response_types_supported": ["code", "token"],
|
|
71
|
+
"grant_types_supported": ["authorization_code", "client_credentials", "implicit"],
|
|
72
|
+
"code_challenge_methods_supported": ["plain", "S256"],
|
|
73
|
+
"scopes_supported": ["read", "write", "openid"],
|
|
74
|
+
"subject_types_supported": ["public"],
|
|
75
|
+
"id_token_signing_alg_values_supported": ["RS256"],
|
|
76
|
+
// Dynamic Client Registration support
|
|
77
|
+
"registration_endpoint": `${baseUrl}/oauth/register`,
|
|
78
|
+
"registration_endpoint_auth_methods_supported": ["none"],
|
|
79
|
+
// Indicate this is an authless server
|
|
80
|
+
"authless": true,
|
|
81
|
+
"require_authentication": false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// OAuth Protected Resource Discovery Response
|
|
86
|
+
function createOAuthProtectedResourceResponse(baseUrl: string) {
|
|
87
|
+
return {
|
|
88
|
+
"resource": baseUrl,
|
|
89
|
+
"authorization_servers": [baseUrl],
|
|
90
|
+
"scopes_supported": ["read", "write"],
|
|
91
|
+
"bearer_methods_supported": ["header", "query"],
|
|
92
|
+
// Indicate no authentication required
|
|
93
|
+
"authless": true,
|
|
94
|
+
"require_authentication": false,
|
|
95
|
+
"token_validation": "none"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Mock OAuth Token Response
|
|
100
|
+
function createMockTokenResponse() {
|
|
101
|
+
return {
|
|
102
|
+
"access_token": "authless-token",
|
|
103
|
+
"token_type": "Bearer",
|
|
104
|
+
"expires_in": 3600,
|
|
105
|
+
"scope": "read write"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Mock Client Registration Response
|
|
110
|
+
function createClientRegistrationResponse() {
|
|
111
|
+
return {
|
|
112
|
+
"client_id": "authless-client",
|
|
113
|
+
"client_secret": "authless-secret",
|
|
114
|
+
"client_id_issued_at": Math.floor(Date.now() / 1000),
|
|
115
|
+
"client_secret_expires_at": 0, // Never expires
|
|
116
|
+
"redirect_uris": [],
|
|
117
|
+
"token_endpoint_auth_method": "none",
|
|
118
|
+
"grant_types": ["authorization_code", "client_credentials"],
|
|
119
|
+
"response_types": ["code"],
|
|
120
|
+
"scope": "read write"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default {
|
|
125
|
+
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
126
|
+
const url = new URL(request.url);
|
|
127
|
+
const baseUrl = getBaseUrl(request);
|
|
128
|
+
|
|
129
|
+
// OAuth Authorization Server Discovery Endpoint
|
|
130
|
+
if (url.pathname === "/.well-known/oauth-authorization-server") {
|
|
131
|
+
return new Response(
|
|
132
|
+
JSON.stringify(createOAuthAuthorizationServerResponse(baseUrl), null, 2),
|
|
133
|
+
{
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
"Access-Control-Allow-Origin": "*",
|
|
137
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
138
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// OAuth Protected Resource Discovery Endpoint
|
|
145
|
+
if (url.pathname === "/.well-known/oauth-protected-resource") {
|
|
146
|
+
return new Response(
|
|
147
|
+
JSON.stringify(createOAuthProtectedResourceResponse(baseUrl), null, 2),
|
|
148
|
+
{
|
|
149
|
+
headers: {
|
|
150
|
+
"Content-Type": "application/json",
|
|
151
|
+
"Access-Control-Allow-Origin": "*",
|
|
152
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
153
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Mock OAuth Authorization Endpoint
|
|
160
|
+
if (url.pathname === "/oauth/authorize") {
|
|
161
|
+
|
|
162
|
+
// redirect to the redirect_uri if provided
|
|
163
|
+
const redirectUri = url.searchParams.get("redirect_uri");
|
|
164
|
+
if (redirectUri) {
|
|
165
|
+
return Response.redirect(redirectUri);
|
|
166
|
+
}
|
|
167
|
+
return new Response(
|
|
168
|
+
JSON.stringify(createMockTokenResponse(), null, 2),
|
|
169
|
+
{
|
|
170
|
+
headers: {
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
"Access-Control-Allow-Origin": "*",
|
|
173
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
174
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Mock OAuth Token Endpoint
|
|
181
|
+
if (url.pathname === "/oauth/token") {
|
|
182
|
+
return new Response(
|
|
183
|
+
JSON.stringify(createMockTokenResponse(), null, 2),
|
|
184
|
+
{
|
|
185
|
+
headers: {
|
|
186
|
+
"Content-Type": "application/json",
|
|
187
|
+
"Access-Control-Allow-Origin": "*",
|
|
188
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
189
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Mock OAuth Client Registration Endpoint
|
|
196
|
+
if (url.pathname === "/oauth/register") {
|
|
197
|
+
// Handle both GET and POST for client registration
|
|
198
|
+
if (request.method === "POST" || request.method === "GET") {
|
|
199
|
+
return new Response(
|
|
200
|
+
JSON.stringify(createClientRegistrationResponse(), null, 2),
|
|
201
|
+
{
|
|
202
|
+
headers: {
|
|
203
|
+
"Content-Type": "application/json",
|
|
204
|
+
"Access-Control-Allow-Origin": "*",
|
|
205
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
206
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Health check endpoint
|
|
214
|
+
if (url.pathname === "/health") {
|
|
215
|
+
return new Response(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
status: "ok",
|
|
218
|
+
authless: true,
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
server: "mcp-server-chart"
|
|
221
|
+
}, null, 2),
|
|
222
|
+
{
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
"Access-Control-Allow-Origin": "*",
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Root endpoint with server info
|
|
232
|
+
if (url.pathname === "/") {
|
|
233
|
+
return new Response(
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
name: "mcp-server-chart",
|
|
236
|
+
version: "1.0.0",
|
|
237
|
+
authless: true,
|
|
238
|
+
endpoints: {
|
|
239
|
+
mcp: "/mcp",
|
|
240
|
+
sse: "/sse",
|
|
241
|
+
health: "/health",
|
|
242
|
+
oauth_authorization_server: "/.well-known/oauth-authorization-server",
|
|
243
|
+
oauth_protected_resource: "/.well-known/oauth-protected-resource",
|
|
244
|
+
oauth_authorize: "/oauth/authorize",
|
|
245
|
+
oauth_token: "/oauth/token"
|
|
246
|
+
}
|
|
247
|
+
}, null, 2),
|
|
248
|
+
{
|
|
249
|
+
headers: {
|
|
250
|
+
"Content-Type": "application/json",
|
|
251
|
+
"Access-Control-Allow-Origin": "*",
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Handle CORS preflight requests
|
|
258
|
+
if (request.method === "OPTIONS") {
|
|
259
|
+
return new Response(null, {
|
|
260
|
+
headers: {
|
|
261
|
+
"Access-Control-Allow-Origin": "*",
|
|
262
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
263
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Existing MCP endpoints
|
|
269
|
+
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
|
|
270
|
+
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (url.pathname === "/mcp") {
|
|
274
|
+
return MyMCP.serve("/mcp").fetch(request, env, ctx);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return new Response("Not found", { status: 404 });
|
|
278
|
+
},
|
|
279
|
+
};
|